From: Marko Ratkaj Date: Tue, 18 Sep 2018 14:33:22 +0000 (+0200) Subject: generic: split files according to kernel versions X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=78c522f28e6fcbad2a10550ba581172af32ae054;p=openwrt%2Fstaging%2Fwigyori.git generic: split files according to kernel versions Signed-off-by: Marko Ratkaj --- diff --git a/target/linux/generic/files-3.18/Documentation/networking/adm6996.txt b/target/linux/generic/files-3.18/Documentation/networking/adm6996.txt new file mode 100644 index 0000000000..ab59f1df03 --- /dev/null +++ b/target/linux/generic/files-3.18/Documentation/networking/adm6996.txt @@ -0,0 +1,110 @@ +------- + +ADM6996FC / ADM6996M switch chip driver + + +1. General information + + This driver supports the FC and M models only. The ADM6996F and L are + completely different chips. + + Support for the FC model is extremely limited at the moment. There is no VLAN + support as of yet. The driver will not offer an swconfig interface for the FC + chip. + +1.1 VLAN IDs + + It is possible to define 16 different VLANs. Every VLAN has an identifier, its + VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the + swconfig based configuration is very straightforward. To define two VLANs with + IDs 4 and 5, you can invoke, for example: + + # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' + # swconfig dev ethX vlan 5 set ports '0t 1t 5t' + + The swconfig framework will automatically invoke 'port Y set pvid Z' for every + port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In + this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port + is the VLAN ID associated with untagged packets coming in on that port. + + But if you wish to use VLAN IDs outside the range 0-15, this automatic + behaviour of the swconfig framework becomes a problem. The 16 VLANs that + swconfig can configure on the ADM6996 also have a "vid" setting. By default, + this is the same as the number of the VLAN entry, to make the simple behaviour + above possible. To still support a VLAN with a VLAN ID higher than 15 + (presumably because you are in a network where such VLAN IDs are already in + use), you can change the "vid" setting of the VLAN to anything in the range + 0-1023. But suppose you did the following: + + # swconfig dev ethX vlan 0 set vid 998 + # swconfig dev ethX vlan 0 set ports '0 2 5t' + + Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid + 0'. But the "pvid" should be set to 998, so you are responsible for manually + fixing this! + +1.2 VLAN filtering + + The switch is configured to apply source port filtering. This means that + packets are only accepted when the port the packets came in on is a member of + the VLAN the packet should go to. + + Only membership of a VLAN is tested, it does not matter whether it is a tagged + or untagged membership. + + For untagged packets, the destination VLAN is the Primary VLAN ID of the + incoming port. So if the PVID of a port is 0, but that port is not a member of + the VLAN with ID 0, this means that untagged packets on that port are dropped. + This can be used as a roundabout way of dropping untagged packets from a port, + a mode often referred to as "Admit only tagged packets". + +1.3 Reset + + The two supported chip models do not have a sofware-initiated reset. When the + driver is initialised, as well as when the 'reset' swconfig option is invoked, + the driver will set those registers it knows about and supports to the correct + default value. But there are a lot of registers in the chip that the driver + does not support. If something changed those registers, invoking 'reset' or + performing a warm reboot might still leave the chip in a "broken" state. Only + a hardware reset will bring it back in the default state. + +2. Technical details on PHYs and the ADM6996 + + From the viewpoint of the Linux kernel, it is common that an Ethernet adapter + can be seen as a separate MAC entity and a separate PHY entity. The PHY entity + can be queried and set through registers accessible via an MDIO bus. A PHY + normally has a single address on that bus, in the range 0 through 31. + + The ADM6996 has special-purpose registers in the range of PHYs 0 through 10. + Even though all these registers control a single ADM6996 chip, the Linux + kernel treats this as 11 separate PHYs. The driver will bind to these + addresses to prevent a different PHY driver from binding and corrupting these + registers. + + What Linux sees as the PHY on address 0 is meant for the Ethernet MAC + connected to the CPU port of the ADM6996 switch chip (port 5). This is the + Ethernet MAC you will use to send and receive data through the switch. + + The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of + the switch chip. These can be accessed with the Generic PHY driver, as the + registers have the common layout. + + If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC + needs to bind to PHY address 20 for the port to work correctly. + + The ADM6996 switch driver will reset the ports 0 through 3 on startup and when + 'reset' is invoked. This could clash with a different PHY driver if the kernel + binds a PHY driver to address 16 through 19. + + If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996 + driver will simply always report a connected 100 Mbit/s full-duplex link for + that PHY, and provide no other functionality. This is most likely not what you + want. So if you see a message in your log + + ethX: PHY overlaps ADM6996, providing fixed PHY yy. + + This is most likely an indication that ethX will not work properly, and your + kernel needs to be configured to attach a different PHY to that Ethernet MAC. + + Controlling the mapping between MACs and PHYs is usually done in platform- or + board-specific fixup code. The ADM6996 driver has no influence over this. diff --git a/target/linux/generic/files-3.18/arch/mips/fw/myloader/Makefile b/target/linux/generic/files-3.18/arch/mips/fw/myloader/Makefile new file mode 100644 index 0000000000..34acfd01cc --- /dev/null +++ b/target/linux/generic/files-3.18/arch/mips/fw/myloader/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Compex's MyLoader support on MIPS architecture +# + +lib-y += myloader.o diff --git a/target/linux/generic/files-3.18/arch/mips/fw/myloader/myloader.c b/target/linux/generic/files-3.18/arch/mips/fw/myloader/myloader.c new file mode 100644 index 0000000000..a26f9ad3fd --- /dev/null +++ b/target/linux/generic/files-3.18/arch/mips/fw/myloader/myloader.c @@ -0,0 +1,63 @@ +/* + * Compex's MyLoader specific prom routines + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include + +#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800) +#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00) +#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00) +#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00) + +static struct myloader_info myloader_info __initdata; +static int myloader_found __initdata; + +struct myloader_info * __init myloader_get_info(void) +{ + struct mylo_system_params *sysp; + struct mylo_board_params *boardp; + struct mylo_partition_table *parts; + + if (myloader_found) + return &myloader_info; + + sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR); + boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR); + parts = (struct mylo_partition_table *)(PART_TABLE_ADDR); + + printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n", + sysp->magic, boardp->magic, parts->magic); + + /* Check for some magic numbers */ + if (sysp->magic != MYLO_MAGIC_SYS_PARAMS || + boardp->magic != MYLO_MAGIC_BOARD_PARAMS || + le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS) + return NULL; + + printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n", + sysp->vid, sysp->did, sysp->svid, sysp->sdid); + + myloader_info.vid = sysp->vid; + myloader_info.did = sysp->did; + myloader_info.svid = sysp->svid; + myloader_info.sdid = sysp->sdid; + + memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs)); + + myloader_found = 1; + + return &myloader_info; +} diff --git a/target/linux/generic/files-3.18/drivers/leds/ledtrig-netdev.c b/target/linux/generic/files-3.18/drivers/leds/ledtrig-netdev.c new file mode 100644 index 0000000000..8d3249010d --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/leds/ledtrig-netdev.c @@ -0,0 +1,444 @@ +/* + * LED Kernel Netdev Trigger + * + * Toggles the LED to reflect the link and traffic state of a named net device + * + * Copyright 2007 Oliver Jowett + * + * Derived from ledtrig-timer.c which is: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds.h" + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * + * interval - duration of LED blink, in milliseconds + * + * mode - either "none" (LED is off) or a space separated list of one or more of: + * link: LED's normal state reflects whether the link is up (has carrier) or not + * tx: LED blinks on transmitted data + * rx: LED blinks on receive data + * + * Some suggestions: + * + * Simple link status LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo link >someled/mode + * + * Ethernet-style link/activity LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo "link tx rx" >someled/mode + * + * Modem-style tx/rx LEDs: + * $ echo netdev >led1/trigger + * $ echo ppp0 >led1/device_name + * $ echo tx >led1/mode + * $ echo netdev >led2/trigger + * $ echo ppp0 >led2/device_name + * $ echo rx >led2/mode + * + */ + +#define MODE_LINK 1 +#define MODE_TX 2 +#define MODE_RX 4 + +struct led_netdev_data { + spinlock_t lock; + + struct delayed_work work; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + unsigned interval; + unsigned mode; + unsigned link_up; + unsigned last_activity; +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) + led_set_brightness(trigger_data->led_cdev, LED_FULL); + else + led_set_brightness(trigger_data->led_cdev, LED_OFF); + + if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static ssize_t led_device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%s\n", trigger_data->device_name); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (size >= IFNAMSIZ) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + strcpy(trigger_data->device_name, buf); + if (size > 0 && trigger_data->device_name[size-1] == '\n') + trigger_data->device_name[size-1] = 0; + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + if (trigger_data->device_name[0] != 0) { + /* check for existing device to update from */ + trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); + if (trigger_data->net_dev != NULL) + trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; + } + + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); + +static ssize_t led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->mode == 0) { + strcpy(buf, "none\n"); + } else { + if (trigger_data->mode & MODE_LINK) + strcat(buf, "link "); + if (trigger_data->mode & MODE_TX) + strcat(buf, "tx "); + if (trigger_data->mode & MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf)+1; +} + +static ssize_t led_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode == -1) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= MODE_LINK; + else + return -EINVAL; + } + + if (new_mode == -1) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->mode = new_mode; + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); + +static ssize_t led_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long value = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (isspace(*after)) + count++; + + /* impose some basic bounds on the timer interval */ + if (count == size && value >= 5 && value <= 10000) { + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->interval = msecs_to_jiffies(value); + set_baseline_state(trigger_data); /* resets timer */ + spin_unlock_bh(&trigger_data->lock); + + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, + void *dv) +{ + struct net_device *dev = netdev_notifier_info_to_dev((struct netdev_notifier_info *) dv); + struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER && evt != NETDEV_CHANGENAME) + return NOTIFY_DONE; + + if (strcmp(dev->name, trigger_data->device_name)) + return NOTIFY_DONE; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (evt == NETDEV_REGISTER || evt == NETDEV_CHANGENAME) { + if (trigger_data->net_dev != NULL) + dev_put(trigger_data->net_dev); + + dev_hold(dev); + trigger_data->net_dev = dev; + trigger_data->link_up = 0; + goto done; + } + + if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + goto done; + } + + /* UP / DOWN / CHANGE */ + + trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); + set_baseline_state(trigger_data); + +done: + spin_unlock_bh(&trigger_data->lock); + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_work(struct work_struct *work) +{ + struct led_netdev_data *trigger_data = container_of(work, struct led_netdev_data, work.work); + struct rtnl_link_stats64 *dev_stats; + unsigned new_activity; + struct rtnl_link_stats64 temp; + + if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { + /* we don't need to do timer work, just reflect link state. */ + led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); + return; + } + + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + new_activity = + ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + + ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); + + if (trigger_data->mode & MODE_LINK) { + /* base state is ON (link present) */ + /* if there's no link, we don't get this far and the LED is off */ + + /* OFF -> ON always */ + /* ON -> OFF on activity */ + if (trigger_data->led_cdev->brightness == LED_OFF) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } + } else { + /* base state is OFF */ + /* ON -> OFF always */ + /* OFF -> ON on activity */ + if (trigger_data->led_cdev->brightness == LED_FULL) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } + } + + trigger_data->last_activity = new_activity; + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static void netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return; + + spin_lock_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + trigger_data->interval = msecs_to_jiffies(50); + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + led_cdev->trigger_data = trigger_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_mode); + if (rc) + goto err_out_device_name; + rc = device_create_file(led_cdev->dev, &dev_attr_interval); + if (rc) + goto err_out_mode; + + register_netdevice_notifier(&trigger_data->notifier); + return; + +err_out_mode: + device_remove_file(led_cdev->dev, &dev_attr_mode); +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(trigger_data); +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (trigger_data) { + unregister_netdevice_notifier(&trigger_data->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_mode); + device_remove_file(led_cdev->dev, &dev_attr_interval); + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + spin_unlock_bh(&trigger_data->lock); + + kfree(trigger_data); + } +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, +}; + +static int __init netdev_trig_init(void) +{ + return led_trigger_register(&netdev_led_trigger); +} + +static void __exit netdev_trig_exit(void) +{ + led_trigger_unregister(&netdev_led_trigger); +} + +module_init(netdev_trig_init); +module_exit(netdev_trig_exit); + +MODULE_AUTHOR("Oliver Jowett "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-3.18/drivers/misc/owl-loader.c b/target/linux/generic/files-3.18/drivers/misc/owl-loader.c new file mode 100644 index 0000000000..f11cb2b49c --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/misc/owl-loader.c @@ -0,0 +1,246 @@ +/* + * Initialize Owl Emulation Devices + * + * Copyright (C) 2016 Christian Lamparter + * Copyright (C) 2016 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) + * need to be able to initialize the PCIe wifi device. Normally, this is done + * during the early stages of booting linux, because the necessary init code + * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup. + * However,this isn't possible for devices which have the init code for the + * Atheros chip stored on NAND. Hence, this module can be used to initialze + * the chip when the user-space is ready to extract the init code. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct owl_ctx { + struct completion eeprom_load; +}; + +#define EEPROM_FILENAME_LEN 100 + +#define AR5416_EEPROM_MAGIC 0xa55a + +static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, + size_t cal_len) +{ + void __iomem *mem; + const void *cal_end = (void *)cal_data + cal_len; + const struct { + __be16 reg; + __be16 low_val; + __be16 high_val; + } __packed *data; + u16 cmd; + u32 bar0; + bool swap_needed = false; + + if (*cal_data != AR5416_EEPROM_MAGIC) { + if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { + dev_err(&pdev->dev, "invalid calibration data\n"); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "calibration data needs swapping\n"); + swap_needed = true; + } + + dev_info(&pdev->dev, "fixup device configuration\n"); + + mem = pcim_iomap(pdev, 0, 0); + if (!mem) { + dev_err(&pdev->dev, "ioremap error\n"); + return -EINVAL; + } + + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, + pci_resource_start(pdev, 0)); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + /* set pointer to first reg address */ + for (data = (const void *) (cal_data + 3); + (const void *) data <= cal_end && data->reg != cpu_to_be16(~0); + data++) { + u32 val; + u16 reg; + + reg = data->reg; + val = data->low_val; + val |= data->high_val << 16; + + if (swap_needed) { + reg = swab16(reg); + val = swahb32(val); + } + +#ifdef CONFIG_LANTIQ + val = swab32(val); +#endif + + __raw_writel(val, mem + reg); + udelay(100); + } + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); + pcim_iounmap(pdev, mem); + + pci_disable_device(pdev); + + return 0; +} + +static void owl_fw_cb(const struct firmware *fw, void *context) +{ + struct pci_dev *pdev = (struct pci_dev *) context; + struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev); + struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct pci_bus *bus; + + complete(&ctx->eeprom_load); + + if (!fw) { + dev_err(&pdev->dev, "no eeprom data received.\n"); + goto release; + } + + /* also note that we are doing *u16 operations on the file */ + if (fw->size > sizeof(pdata->eeprom_data) || fw->size < 0x200 || + (fw->size & 1) == 1) { + dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); + goto release; + } + + if (pdata) { + memcpy(pdata->eeprom_data, fw->data, fw->size); + + /* + * eeprom has been successfully loaded - pass the data to ath9k + * but remove the eeprom_name, so it doesn't try to load it too. + */ + pdata->eeprom_name = NULL; + } + + if (ath9k_pci_fixup(pdev, (const u16 *) fw->data, fw->size)) + goto release; + + pci_lock_rescan_remove(); + bus = pdev->bus; + pci_stop_and_remove_bus_device(pdev); + /* + * the device should come back with the proper + * ProductId. But we have to initiate a rescan. + */ + pci_rescan_bus(bus); + pci_unlock_rescan_remove(); + +release: + release_firmware(fw); +} + +static const char *owl_get_eeprom_name(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct ath9k_platform_data *pdata; + char *eeprom_name; + + /* try the existing platform data first */ + pdata = dev_get_platdata(dev); + if (pdata && pdata->eeprom_name) + return pdata->eeprom_name; + + dev_dbg(dev, "using auto-generated eeprom filename\n"); + + eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); + if (!eeprom_name) + return NULL; + + /* this should match the pattern used in ath9k/init.c */ + scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", + dev_name(dev)); + + return eeprom_name; +} + +static int owl_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct owl_ctx *ctx; + const char *eeprom_name; + int err = 0; + + if (pcim_enable_device(pdev)) + return -EIO; + + pcim_pin_device(pdev); + + eeprom_name = owl_get_eeprom_name(pdev); + if (!eeprom_name) { + dev_err(&pdev->dev, "no eeprom filename found.\n"); + return -ENODEV; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + dev_err(&pdev->dev, "failed to alloc device context.\n"); + return -ENOMEM; + } + init_completion(&ctx->eeprom_load); + + pci_set_drvdata(pdev, ctx); + err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, + &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); + if (err) { + dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); + kfree(ctx); + } + return err; +} + +static void owl_remove(struct pci_dev *pdev) +{ + struct owl_ctx *ctx = pci_get_drvdata(pdev); + + if (ctx) { + wait_for_completion(&ctx->eeprom_load); + pci_set_drvdata(pdev, NULL); + kfree(ctx); + } +} + +static const struct pci_device_id owl_pci_table[] = { + { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ + { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ + { }, +}; +MODULE_DEVICE_TABLE(pci, owl_pci_table); + +static struct pci_driver owl_driver = { + .name = "owl-loader", + .id_table = owl_pci_table, + .probe = owl_probe, + .remove = owl_remove, +}; +module_pci_driver(owl_driver); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Kconfig new file mode 100644 index 0000000000..81ece43db8 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Kconfig @@ -0,0 +1,76 @@ +config MTD_SPLIT + def_bool n + help + Generic MTD split support. + +config MTD_SPLIT_SUPPORT + def_bool MTD = y + +comment "Rootfs partition parsers" + +config MTD_SPLIT_SQUASHFS_ROOT + bool "Squashfs based root partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + default n + help + This provides a parsing function which allows to detect the + offset and size of the unused portion of a rootfs partition + containing a squashfs. + +comment "Firmware partition parsers" + +config MTD_SPLIT_SEAMA_FW + bool "Seama firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_WRGG_FW + bool "WRGG firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_UIMAGE_FW + bool "uImage based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_FIT_FW + bool "FIT based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_LZMA_FW + bool "LZMA compressed kernel based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TPLINK_FW + bool "TP-Link firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TRX_FW + bool "TRX image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_BRNIMAGE_FW + bool "brnImage (brnboot image) firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_EVA_FW + bool "EVA image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_MINOR_FW + bool "Mikrotik NOR image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_JIMAGE_FW + bool "JBOOT Image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Makefile new file mode 100644 index 0000000000..206e754a18 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o +obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o +obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o +obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o +obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o +obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o +obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o +obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o +obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o +obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o +obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o +obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o +obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.c new file mode 100644 index 0000000000..b2e51dcfc6 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) "mtdsplit: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define UBI_EC_MAGIC 0x55424923 /* UBI# */ + +struct squashfs_super_block { + __le32 s_magic; + __le32 pad0[9]; + __le64 bytes_used; +}; + +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + struct squashfs_super_block sb; + size_t retlen; + int err; + + err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb); + if (err || (retlen != sizeof(sb))) { + pr_alert("error occured while reading from \"%s\"\n", + master->name); + return -EIO; + } + + if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) { + pr_alert("no squashfs found in \"%s\"\n", master->name); + return -EINVAL; + } + + retlen = le64_to_cpu(sb.bytes_used); + if (retlen <= 0) { + pr_alert("squashfs is empty in \"%s\"\n", master->name); + return -ENODEV; + } + + if (offset + retlen > master->size) { + pr_alert("squashfs has invalid size in \"%s\"\n", + master->name); + return -EINVAL; + } + + *squashfs_len = retlen; + return 0; +} +EXPORT_SYMBOL_GPL(mtd_get_squashfs_len); + +static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset) +{ + return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize; +} + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + u32 magic; + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (ret) + return ret; + + if (retlen != sizeof(magic)) + return -EIO; + + if (le32_to_cpu(magic) == SQUASHFS_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_SQUASHFS; + return 0; + } else if (magic == 0x19852003) { + if (type) + *type = MTDSPLIT_PART_TYPE_JFFS2; + return 0; + } else if (be32_to_cpu(magic) == UBI_EC_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_UBI; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + size_t offset; + int err; + + for (offset = from; offset < limit; + offset = mtd_next_eb(mtd, offset)) { + err = mtd_check_rootfs_magic(mtd, offset, type); + if (err) + continue; + + *ret_offset = offset; + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(mtd_find_rootfs_from); + diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.h b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.h new file mode 100644 index 0000000000..71d62a8956 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MTDSPLIT_H +#define _MTDSPLIT_H + +#define KERNEL_PART_NAME "kernel" +#define ROOTFS_PART_NAME "rootfs" +#define UBI_PART_NAME "ubi" + +#define ROOTFS_SPLIT_NAME "rootfs_data" + +enum mtdsplit_part_type { + MTDSPLIT_PART_TYPE_UNK = 0, + MTDSPLIT_PART_TYPE_SQUASHFS, + MTDSPLIT_PART_TYPE_JFFS2, + MTDSPLIT_PART_TYPE_UBI, +}; + +#ifdef CONFIG_MTD_SPLIT +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len); + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type); + +#else +static inline int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + return -ENODEV; +} + +static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + return -EINVAL; +} + +static inline int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + return -ENODEV; +} +#endif /* CONFIG_MTD_SPLIT */ + +#endif /* _MTDSPLIT_H */ diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_brnimage.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_brnimage.c new file mode 100644 index 0000000000..3f2d79601a --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_brnimage.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define BRNIMAGE_NR_PARTS 2 + +#define BRNIMAGE_ALIGN_BYTES 0x400 +#define BRNIMAGE_FOOTER_SIZE 12 + +#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE) +#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE) + +static int mtdsplit_parse_brnimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + uint32_t buf; + unsigned long rootfs_offset, rootfs_size, kernel_size; + size_t len; + int ret = 0; + + for (rootfs_offset = 0; rootfs_offset < master->size; + rootfs_offset += BRNIMAGE_ALIGN_BYTES) { + ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (!ret) + break; + } + + if (ret) + return ret; + + if (rootfs_offset >= master->size) + return -EINVAL; + + ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len, + (void *)&buf); + if (ret) + return ret; + + if (len != 4) + return -EIO; + + kernel_size = le32_to_cpu(buf); + + if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD)) + return -EINVAL; + + if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD)) + return -EINVAL; + + /* + * The footer must be untouched as it contains the checksum of the + * original brnImage (kernel + squashfs)! + */ + rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE; + + parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return BRNIMAGE_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_brnimage_parser = { + .owner = THIS_MODULE, + .name = "brnimage-fw", + .parse_fn = mtdsplit_parse_brnimage, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_brnimage_init(void) +{ + register_mtd_parser(&mtdsplit_brnimage_parser); + + return 0; +} + +subsys_initcall(mtdsplit_brnimage_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_eva.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_eva.c new file mode 100644 index 0000000000..746944ee2e --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_eva.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define EVA_NR_PARTS 2 +#define EVA_MAGIC 0xfeed1281 +#define EVA_FOOTER_SIZE 0x18 +#define EVA_DUMMY_SQUASHFS_SIZE 0x100 + +struct eva_image_header { + uint32_t magic; + uint32_t size; +}; + +static int mtdsplit_parse_eva(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct eva_image_header hdr; + size_t retlen; + unsigned long kernel_size, rootfs_offset; + int err; + + err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != sizeof(hdr)) + return -EIO; + + if (le32_to_cpu(hdr.magic) != EVA_MAGIC) + return -EINVAL; + + kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE; + + /* rootfs starts at the next 0x10000 boundary: */ + rootfs_offset = round_up(kernel_size, 0x10000); + + /* skip the dummy EVA squashfs partition (with wrong endianness): */ + rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE; + + if (rootfs_offset >= master->size) + return -EINVAL; + + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return EVA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_eva_parser = { + .owner = THIS_MODULE, + .name = "eva-fw", + .parse_fn = mtdsplit_parse_eva, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_eva_init(void) +{ + register_mtd_parser(&mtdsplit_eva_parser); + + return 0; +} + +subsys_initcall(mtdsplit_eva_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_fit.c new file mode 100644 index 0000000000..f356adcd4e --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_fit.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 The Linux Foundation + * Copyright (C) 2014 Gabor Juhos + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +struct fdt_header { + uint32_t magic; /* magic word FDT_MAGIC */ + uint32_t totalsize; /* total size of DT block */ + uint32_t off_dt_struct; /* offset to structure */ + uint32_t off_dt_strings; /* offset to strings */ + uint32_t off_mem_rsvmap; /* offset to memory reserve map */ + uint32_t version; /* format version */ + uint32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + uint32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + uint32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + uint32_t size_dt_struct; /* size of the structure block */ +}; + +static int +mtdsplit_fit_parse(struct mtd_info *mtd, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct fdt_header hdr; + size_t hdr_len, retlen; + size_t offset; + size_t fit_offset, fit_size; + size_t rootfs_offset, rootfs_size; + struct mtd_partition *parts; + int ret; + + hdr_len = sizeof(struct fdt_header); + + /* Parse the MTD device & search for the FIT image location */ + for(offset = 0; offset < mtd->size; offset += mtd->erasesize) { + ret = mtd_read(mtd, 0, hdr_len, &retlen, (void*) &hdr); + if (ret) { + pr_err("read error in \"%s\" at offset 0x%llx\n", + mtd->name, (unsigned long long) offset); + return ret; + } + + if (retlen != hdr_len) { + pr_err("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + /* Check the magic - see if this is a FIT image */ + if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) { + pr_debug("no valid FIT image found in \"%s\" at offset %llx\n", + mtd->name, (unsigned long long) offset); + continue; + } + + /* We found a FIT image. Let's keep going */ + break; + } + + fit_offset = offset; + fit_size = be32_to_cpu(hdr.totalsize); + + if (fit_size == 0) { + pr_err("FIT image in \"%s\" at offset %llx has null size\n", + mtd->name, (unsigned long long) fit_offset); + return -ENODEV; + } + + /* Search for the rootfs partition after the FIT image */ + ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, + &rootfs_offset, NULL); + if (ret) { + pr_info("no rootfs found after FIT image in \"%s\"\n", + mtd->name); + return ret; + } + + rootfs_size = mtd->size - rootfs_offset; + + parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = fit_offset; + parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return 2; +} + +static struct mtd_part_parser uimage_parser = { + .owner = THIS_MODULE, + .name = "fit-fw", + .parse_fn = mtdsplit_fit_parse, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_fit_init(void) +{ + register_mtd_parser(&uimage_parser); + + return 0; +} + +module_init(mtdsplit_fit_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_jimage.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_jimage.c new file mode 100644 index 0000000000..51544a794b --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_jimage.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2018 Paweł Dembicki + * + * Based on: mtdsplit_uimage.c + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE ) + +#define STAG_SIZE 16 +#define STAG_ID 0x04 +#define STAG_MAGIC 0x2B24 + +#define SCH2_SIZE 40 +#define SCH2_MAGIC 0x2124 +#define SCH2_VER 0x02 + +/* + * Jboot image header, + * all data in little endian. + */ + +struct jimage_header //stag + sch2 jboot joined headers +{ + uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id + uint8_t stag_id; // 0x04 + uint16_t stag_magic; //magic 0x2B24 + uint32_t stag_time_stamp; // timestamp calculated in jboot way + uint32_t stag_image_length; // lentgh of kernel + sch2 header + uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel + uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data + uint16_t sch2_magic; // magic 0x2124 + uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma + uint8_t sch2_version; // 0x02 for sch2 + uint32_t sch2_ram_addr; // ram entry address + uint32_t sch2_image_len; // kernel image length + uint32_t sch2_image_crc32; // kernel image crc + uint32_t sch2_start_addr; // ram start address + uint32_t sch2_rootfs_addr; // rootfs flash address + uint32_t sch2_rootfs_len; // rootfls length + uint32_t sch2_rootfs_crc32; // rootfs crc32 + uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero + uint16_t sch2_header_length; // sch2 header length: 0x28 + uint16_t sch2_cmd_line_length; // cmd line length, known zeros +}; + +static int +read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid jImage header if found + */ +static int __mtdsplit_parse_jimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t jimage_offset; + size_t jimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int jimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find jImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct jimage_header *header; + + jimage_size = 0; + + ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid jImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct jimage_header *)(buf + ret); + + jimage_size = sizeof(*header) + header->sch2_image_len + ret; + if ((offset + jimage_size) > master->size) { + pr_debug("jImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (jimage_size == 0) { + pr_debug("no jImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + jimage_offset = offset; + + if (jimage_offset == 0) { + jimage_part = 0; + rf_part = 1; + + /* find the roots after the jImage */ + ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + jimage_size = rootfs_offset - jimage_offset; + } else { + rf_part = 0; + jimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = jimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[jimage_part].name = KERNEL_PART_NAME; + parts[jimage_part].offset = jimage_offset; + parts[jimage_part].size = jimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t jimage_verify_default(u_char *buf, size_t len) +{ + struct jimage_header *header = (struct jimage_header *)buf; + + /* default sanity checks */ + if (header->stag_magic != STAG_MAGIC) { + pr_debug("invalid jImage stag header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_magic != SCH2_MAGIC) { + pr_debug("invalid jImage sch2 header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_cmark != header->stag_id) { + pr_debug("invalid jImage stag header cmark: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_id != STAG_ID) { + pr_debug("invalid jImage stag header id: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_version != SCH2_VER) { + pr_debug("invalid jImage sch2 header version: %02x\n", + header->stag_magic); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_jimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_jimage(master, pparts, data, + jimage_verify_default); +} + +static struct mtd_part_parser jimage_generic_parser = { + .owner = THIS_MODULE, + .name = "jimage-fw", + .parse_fn = mtdsplit_jimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_jimage_init(void) +{ + register_mtd_parser(&jimage_generic_parser); + + return 0; +} + +module_init(mtdsplit_jimage_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_lzma.c new file mode 100644 index 0000000000..b7f044a6dc --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_lzma.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "mtdsplit.h" + +#define LZMA_NR_PARTS 2 +#define LZMA_PROPERTIES_SIZE 5 + +struct lzma_header { + u8 props[LZMA_PROPERTIES_SIZE]; + u8 size_low[4]; + u8 size_high[4]; +}; + +static int mtdsplit_parse_lzma(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct lzma_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + u32 t; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* verify LZMA properties */ + if (hdr.props[0] >= (9 * 5 * 5)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.props[1]); + if (!is_power_of_2(t)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.size_high); + if (t) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return LZMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_lzma_parser = { + .owner = THIS_MODULE, + .name = "lzma-fw", + .parse_fn = mtdsplit_parse_lzma, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_lzma_init(void) +{ + register_mtd_parser(&mtdsplit_lzma_parser); + + return 0; +} + +subsys_initcall(mtdsplit_lzma_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_minor.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_minor.c new file mode 100644 index 0000000000..f971f0a650 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_minor.c @@ -0,0 +1,117 @@ +/* + * MTD splitter for MikroTik NOR devices + * + * Copyright (C) 2017 Thibaut VARENE + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * The rootfs is expected at erase-block boundary due to the use of + * mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header + * for two main reasons: + * - the original header uses weakly defined types (int, enum...) which can + * vary in length depending on build host (and the struct is not packed), + * and the name field can have a different total length depending on + * whether or not the yaffs code was _built_ with unicode support. + * - the only field that could be of real use here (file_size_low) contains + * invalid data in the header generated by kernel2minor, so we cannot use + * it to infer the exact position of the rootfs and do away with + * mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define YAFFS_OBJECT_TYPE_FILE 0x1 +#define YAFFS_OBJECTID_ROOT 0x1 +#define YAFFS_SUM_UNUSED 0xFFFF +#define YAFFS_NAME "kernel" + +#define MINOR_NR_PARTS 2 + +/* + * This structure is based on yaffs_obj_hdr from yaffs_guts.h + * The weak types match upstream. The fields have cpu-endianness + */ +struct minor_header { + int yaffs_type; + int yaffs_obj_id; + u16 yaffs_sum_unused; + char yaffs_name[sizeof(YAFFS_NAME)]; +}; + +static int mtdsplit_parse_minor(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct minor_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* match header */ + if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) + return -EINVAL; + + if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) + return -EINVAL; + + if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) + return -EINVAL; + + if (memcmp(hdr.yaffs_name, YAFFS_NAME, sizeof(YAFFS_NAME))) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return MINOR_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_minor_parser = { + .owner = THIS_MODULE, + .name = "minor-fw", + .parse_fn = mtdsplit_parse_minor, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_minor_init(void) +{ + register_mtd_parser(&mtdsplit_minor_parser); + + return 0; +} + +subsys_initcall(mtdsplit_minor_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_seama.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_seama.c new file mode 100644 index 0000000000..f8556e05a4 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_seama.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define SEAMA_MAGIC 0x5EA3A417 +#define SEAMA_NR_PARTS 2 +#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +struct seama_header { + __be32 magic; /* should always be SEAMA_MAGIC. */ + __be16 reserved; /* reserved for */ + __be16 metasize; /* size of the META data */ + __be32 size; /* size of the image */ + u8 md5[16]; /* digest */ +}; + +static int mtdsplit_parse_seama(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct seama_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) + return -EINVAL; + + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) + + be16_to_cpu(hdr.metasize); + if (kernel_ent_size > master->size) + return -EINVAL; + + /* Check for the rootfs right after Seama entity with a kernel. */ + err = mtd_check_rootfs_magic(master, kernel_ent_size, &type); + if (!err) { + rootfs_offset = kernel_ent_size; + } else { + /* + * On some devices firmware entity might contain both: kernel + * and rootfs. We can't determine kernel size so we just have to + * look for rootfs magic. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + } + + parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize); + parts[0].size = rootfs_offset - parts[0].offset; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[1].name = UBI_PART_NAME; + else + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return SEAMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_seama_parser = { + .owner = THIS_MODULE, + .name = "seama-fw", + .parse_fn = mtdsplit_parse_seama, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_seama_init(void) +{ + register_mtd_parser(&mtdsplit_seama_parser); + + return 0; +} + +subsys_initcall(mtdsplit_seama_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_squashfs.c new file mode 100644 index 0000000000..79e1f73bca --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_squashfs.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +static int +mtdsplit_parse_squashfs(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *part; + struct mtd_info *parent_mtd; + size_t part_offset; + size_t squashfs_len; + int err; + + err = mtd_get_squashfs_len(master, 0, &squashfs_len); + if (err) + return err; + + parent_mtd = mtdpart_get_master(master); + part_offset = mtdpart_get_offset(master); + + part = kzalloc(sizeof(*part), GFP_KERNEL); + if (!part) { + pr_alert("unable to allocate memory for \"%s\" partition\n", + ROOTFS_SPLIT_NAME); + return -ENOMEM; + } + + part->name = ROOTFS_SPLIT_NAME; + part->offset = mtd_roundup_to_eb(part_offset + squashfs_len, + parent_mtd) - part_offset; + part->size = mtd_rounddown_to_eb(master->size - part->offset, master); + + *pparts = part; + return 1; +} + +static struct mtd_part_parser mtdsplit_squashfs_parser = { + .owner = THIS_MODULE, + .name = "squashfs-split", + .parse_fn = mtdsplit_parse_squashfs, + .type = MTD_PARSER_TYPE_ROOTFS, +}; + +static int __init mtdsplit_squashfs_init(void) +{ + register_mtd_parser(&mtdsplit_squashfs_parser); + + return 0; +} + +subsys_initcall(mtdsplit_squashfs_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_tplink.c new file mode 100644 index 0000000000..c346aa8130 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_tplink.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TPLINK_NR_PARTS 2 +#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +#define MD5SUM_LEN 16 + +struct fw_v1 { + char vendor_name[24]; + char fw_version[36]; + uint32_t hw_id; /* hardware id */ + uint32_t hw_rev; /* hardware revision */ + uint32_t unk1; + uint8_t md5sum1[MD5SUM_LEN]; + uint32_t unk2; + uint8_t md5sum2[MD5SUM_LEN]; + uint32_t unk3; + uint32_t kernel_la; /* kernel load address */ + uint32_t kernel_ep; /* kernel entry point */ + uint32_t fw_length; /* total length of the firmware */ + uint32_t kernel_ofs; /* kernel data offset */ + uint32_t kernel_len; /* kernel data length */ + uint32_t rootfs_ofs; /* rootfs data offset */ + uint32_t rootfs_len; /* rootfs data length */ + uint32_t boot_ofs; /* bootloader data offset */ + uint32_t boot_len; /* bootloader data length */ + uint8_t pad[360]; +} __attribute__ ((packed)); + +struct fw_v2 { + char fw_version[48]; /* 0x04: fw version string */ + uint32_t hw_id; /* 0x34: hardware id */ + uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ + uint32_t unk1; /* 0x3c: 0x00000000 */ + uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ + uint32_t unk2; /* 0x50: 0x00000000 */ + uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ + uint32_t unk3; /* 0x64: 0xffffffff */ + + uint32_t kernel_la; /* 0x68: kernel load address */ + uint32_t kernel_ep; /* 0x6c: kernel entry point */ + uint32_t fw_length; /* 0x70: total length of the image */ + uint32_t kernel_ofs; /* 0x74: kernel data offset */ + uint32_t kernel_len; /* 0x78: kernel data length */ + uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ + uint32_t rootfs_len; /* 0x80: rootfs data length */ + uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ + uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ + uint16_t unk4; /* 0x8c: 0x55aa */ + uint8_t sver_hi; /* 0x8e */ + uint8_t sver_lo; /* 0x8f */ + uint8_t unk5; /* 0x90: magic: 0xa5 */ + uint8_t ver_hi; /* 0x91 */ + uint8_t ver_mid; /* 0x92 */ + uint8_t ver_lo; /* 0x93 */ + uint8_t pad[364]; +} __attribute__ ((packed)); + +struct tplink_fw_header { + uint32_t version; + union { + struct fw_v1 v1; + struct fw_v2 v2; + }; +}; + +static int mtdsplit_parse_tplink(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct tplink_fw_header hdr; + size_t hdr_len, retlen, kernel_size; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + switch (le32_to_cpu(hdr.version)) { + case 1: + if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs); + break; + case 2: + case 3: + if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs); + break; + default: + return -EINVAL; + } + + if (kernel_size > master->size) + return -EINVAL; + + /* Find the rootfs */ + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) { + /* + * The size in the header might cover the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, NULL); + if (err) + return err; + } + + parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return TPLINK_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_tplink_parser = { + .owner = THIS_MODULE, + .name = "tplink-fw", + .parse_fn = mtdsplit_parse_tplink, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_tplink_init(void) +{ + register_mtd_parser(&mtdsplit_tplink_parser); + + return 0; +} + +subsys_initcall(mtdsplit_tplink_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_trx.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_trx.c new file mode 100644 index 0000000000..53aebc5987 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_trx.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ + +struct trx_header { + __le32 magic; + __le32 len; + __le32 crc32; + __le32 flag_version; + __le32 offset[4]; +}; + +static int +read_trx_header(struct mtd_info *mtd, size_t offset, + struct trx_header *header) +{ + size_t header_len; + size_t retlen; + int ret; + + header_len = sizeof(*header); + ret = mtd_read(mtd, offset, header_len, &retlen, + (unsigned char *) header); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +static int +mtdsplit_parse_trx(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct trx_header hdr; + int nr_parts; + size_t offset; + size_t trx_offset; + size_t trx_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int ret; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + /* find trx image on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + trx_size = 0; + + ret = read_trx_header(master, offset, &hdr); + if (ret) + continue; + + if (hdr.magic != cpu_to_le32(TRX_MAGIC)) { + pr_debug("no valid trx header found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + + trx_size = le32_to_cpu(hdr.len); + if ((offset + trx_size) > master->size) { + pr_debug("trx image exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (trx_size == 0) { + pr_debug("no trx header found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + trx_offset = offset + hdr.offset[0]; + rootfs_offset = offset + hdr.offset[1]; + rootfs_size = master->size - rootfs_offset; + trx_size = rootfs_offset - trx_offset; + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = trx_offset; + parts[0].size = trx_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return nr_parts; + +err: + kfree(parts); + return ret; +} + +static struct mtd_part_parser trx_parser = { + .owner = THIS_MODULE, + .name = "trx-fw", + .parse_fn = mtdsplit_parse_trx, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_trx_init(void) +{ + register_mtd_parser(&trx_parser); + + return 0; +} + +module_init(mtdsplit_trx_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_uimage.c new file mode 100644 index 0000000000..bd1c723e74 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_uimage.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +/* + * uimage_header itself is only 64B, but it may be prepended with another data. + * Currently the biggest size is for Edimax devices: 20B + 64B + */ +#define MAX_HEADER_LEN 84 + +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_NMLEN 32 /* Image Name Length */ + +#define IH_OS_LINUX 5 /* Linux */ + +#define IH_TYPE_KERNEL 2 /* OS Kernel Image */ +#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image */ + +/* + * Legacy format image header, + * all data in network byte order (aka natural aka bigendian). + */ +struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +static int +read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid uImage header if found + */ +static int __mtdsplit_parse_uimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t uimage_offset; + size_t uimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int uimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find uImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct uimage_header *header; + + uimage_size = 0; + + ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid uImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct uimage_header *)(buf + ret); + + uimage_size = sizeof(*header) + be32_to_cpu(header->ih_size) + ret; + if ((offset + uimage_size) > master->size) { + pr_debug("uImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (uimage_size == 0) { + pr_debug("no uImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + uimage_offset = offset; + + if (uimage_offset == 0) { + uimage_part = 0; + rf_part = 1; + + /* find the roots after the uImage */ + ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + uimage_size = rootfs_offset - uimage_offset; + } else { + rf_part = 0; + uimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = uimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[uimage_part].name = KERNEL_PART_NAME; + parts[uimage_part].offset = uimage_offset; + parts[uimage_part].size = uimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t uimage_verify_default(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + + /* default sanity checks */ + if (be32_to_cpu(header->ih_magic) != IH_MAGIC) { + pr_debug("invalid uImage magic: %08x\n", + be32_to_cpu(header->ih_magic)); + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX) { + pr_debug("invalid uImage OS: %08x\n", + be32_to_cpu(header->ih_os)); + return -EINVAL; + } + + if (header->ih_type != IH_TYPE_KERNEL) { + pr_debug("invalid uImage type: %08x\n", + be32_to_cpu(header->ih_type)); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_uimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_default); +} + +static struct mtd_part_parser uimage_generic_parser = { + .owner = THIS_MODULE, + .name = "uimage-fw", + .parse_fn = mtdsplit_uimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +#define FW_MAGIC_WNR2000V1 0x32303031 +#define FW_MAGIC_WNR2000V3 0x32303033 +#define FW_MAGIC_WNR2000V4 0x32303034 +#define FW_MAGIC_WNR2200 0x32323030 +#define FW_MAGIC_WNR612V2 0x32303631 +#define FW_MAGIC_WNR1000V2 0x31303031 +#define FW_MAGIC_WNR1000V2_VC 0x31303030 +#define FW_MAGIC_WNDR3700 0x33373030 +#define FW_MAGIC_WNDR3700V2 0x33373031 +#define FW_MAGIC_WPN824N 0x31313030 + +static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + uint8_t expected_type = IH_TYPE_FILESYSTEM; + + switch (be32_to_cpu(header->ih_magic)) { + case FW_MAGIC_WNR612V2: + case FW_MAGIC_WNR1000V2: + case FW_MAGIC_WNR1000V2_VC: + case FW_MAGIC_WNR2000V1: + case FW_MAGIC_WNR2000V3: + case FW_MAGIC_WNR2200: + case FW_MAGIC_WNDR3700: + case FW_MAGIC_WNDR3700V2: + case FW_MAGIC_WPN824N: + break; + case FW_MAGIC_WNR2000V4: + expected_type = IH_TYPE_KERNEL; + break; + default: + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX || + header->ih_type != expected_type) + return -EINVAL; + + return 0; +} + +static int +mtdsplit_uimage_parse_netgear(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_wndr3700); +} + +static struct mtd_part_parser uimage_netgear_parser = { + .owner = THIS_MODULE, + .name = "netgear-fw", + .parse_fn = mtdsplit_uimage_parse_netgear, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Edimax + **************************************************/ + +#define FW_EDIMAX_OFFSET 20 +#define FW_MAGIC_EDIMAX 0x43535953 + +static ssize_t uimage_find_edimax(u_char *buf, size_t len) +{ + u32 *magic; + + if (len < FW_EDIMAX_OFFSET + sizeof(struct uimage_header)) { + pr_err("Buffer too small for checking Edimax header\n"); + return -ENOSPC; + } + + magic = (u32 *)buf; + if (be32_to_cpu(*magic) != FW_MAGIC_EDIMAX) + return -EINVAL; + + if (!uimage_verify_default(buf + FW_EDIMAX_OFFSET, len)) + return FW_EDIMAX_OFFSET; + + return -EINVAL; +} + +static int +mtdsplit_uimage_parse_edimax(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_find_edimax); +} + +static struct mtd_part_parser uimage_edimax_parser = { + .owner = THIS_MODULE, + .name = "edimax-fw", + .parse_fn = mtdsplit_uimage_parse_edimax, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_uimage_init(void) +{ + register_mtd_parser(&uimage_generic_parser); + register_mtd_parser(&uimage_netgear_parser); + register_mtd_parser(&uimage_edimax_parser); + + return 0; +} + +module_init(mtdsplit_uimage_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_wrgg.c b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_wrgg.c new file mode 100644 index 0000000000..16ebd51345 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/mtdsplit/mtdsplit_wrgg.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2016 Stijn Tintel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define WRGG_NR_PARTS 2 +#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ +#define WRGG03_MAGIC 0x20080321 +#define WRG_MAGIC 0x20040220 + +struct wrgg03_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + char version[16]; + char model[16]; + uint32_t flag[2]; + uint32_t reserve[2]; + char buildno[16]; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + +struct wrg_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + + +static int mtdsplit_parse_wrgg(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct wrgg03_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) { + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size); + } else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) { + kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu( + ((struct wrg_header*)&hdr)->size); + } else { + return -EINVAL; + } + + if (kernel_ent_size > master->size) + return -EINVAL; + + /* + * The size in the header covers the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + + parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return WRGG_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_wrgg_parser = { + .owner = THIS_MODULE, + .name = "wrgg-fw", + .parse_fn = mtdsplit_parse_wrgg, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_wrgg_init(void) +{ + register_mtd_parser(&mtdsplit_wrgg_parser); + + return 0; +} + +subsys_initcall(mtdsplit_wrgg_init); diff --git a/target/linux/generic/files-3.18/drivers/mtd/myloader.c b/target/linux/generic/files-3.18/drivers/mtd/myloader.c new file mode 100644 index 0000000000..7532d454ed --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/mtd/myloader.c @@ -0,0 +1,182 @@ +/* + * Parse MyLoader-style flash partition tables and produce a Linux partition + * array to match. + * + * Copyright (C) 2007-2009 Gabor Juhos + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BLOCK_LEN_MIN 0x10000 +#define PART_NAME_LEN 32 + +struct part_data { + struct mylo_partition_table tab; + char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; +}; + +static int myloader_parse_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct part_data *buf; + struct mylo_partition_table *tab; + struct mylo_partition *part; + struct mtd_partition *mtd_parts; + struct mtd_partition *mtd_part; + int num_parts; + int ret, i; + size_t retlen; + char *names; + unsigned long offset; + unsigned long blocklen; + + buf = vmalloc(sizeof(*buf)); + if (!buf) { + return -ENOMEM; + goto out; + } + tab = &buf->tab; + + blocklen = master->erasesize; + if (blocklen < BLOCK_LEN_MIN) + blocklen = BLOCK_LEN_MIN; + + offset = blocklen; + + /* Find the partition table */ + for (i = 0; i < 4; i++, offset += blocklen) { + printk(KERN_DEBUG "%s: searching for MyLoader partition table" + " at offset 0x%lx\n", master->name, offset); + + ret = mtd_read(master, offset, sizeof(*buf), &retlen, + (void *)buf); + if (ret) + goto out_free_buf; + + if (retlen != sizeof(*buf)) { + ret = -EIO; + goto out_free_buf; + } + + /* Check for Partition Table magic number */ + if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) + break; + + } + + if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { + printk(KERN_DEBUG "%s: no MyLoader partition table found\n", + master->name); + ret = 0; + goto out_free_buf; + } + + /* The MyLoader and the Partition Table is always present */ + num_parts = 2; + + /* Detect number of used partitions */ + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + num_parts++; + } + + mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + + num_parts * PART_NAME_LEN), GFP_KERNEL); + + if (!mtd_parts) { + ret = -ENOMEM; + goto out_free_buf; + } + + mtd_part = mtd_parts; + names = (char *)&mtd_parts[num_parts]; + + strncpy(names, "myloader", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = 0; + mtd_part->size = offset; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + strncpy(names, "partition_table", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = offset; + mtd_part->size = blocklen; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) + strncpy(names, buf->names[i], PART_NAME_LEN); + else + snprintf(names, PART_NAME_LEN, "partition%d", i); + + mtd_part->offset = le32_to_cpu(part->addr); + mtd_part->size = le32_to_cpu(part->size); + mtd_part->name = names; + mtd_part++; + names += PART_NAME_LEN; + } + + *pparts = mtd_parts; + ret = num_parts; + + out_free_buf: + vfree(buf); + out: + return ret; +} + +static struct mtd_part_parser myloader_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = myloader_parse_partitions, + .name = "MyLoader", +}; + +static int __init myloader_mtd_parser_init(void) +{ + register_mtd_parser(&myloader_mtd_parser); + + return 0; +} + +static void __exit myloader_mtd_parser_exit(void) +{ + deregister_mtd_parser(&myloader_mtd_parser); +} + +module_init(myloader_mtd_parser_init); +module_exit(myloader_mtd_parser_exit); + +MODULE_AUTHOR("Gabor Juhos "); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/adm6996.c b/target/linux/generic/files-3.18/drivers/net/phy/adm6996.c new file mode 100644 index 0000000000..42928bab07 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/adm6996.c @@ -0,0 +1,1241 @@ +/* + * ADM6996 switch driver + * + * swconfig interface based on ar8216.c + * + * Copyright (c) 2008 Felix Fietkau + * VLAN support Copyright (c) 2010, 2011 Peter Lebbing + * Copyright (c) 2013 Hauke Mehrtens + * Copyright (c) 2014 Matti Laakso + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/*#define DEBUG 1*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau, Peter Lebbing "); +MODULE_LICENSE("GPL"); + +static const char * const adm6996_model_name[] = +{ + NULL, + "ADM6996FC", + "ADM6996M", + "ADM6996L" +}; + +struct adm6996_mib_desc { + unsigned int offset; + const char *name; +}; + +struct adm6996_priv { + struct switch_dev dev; + void *priv; + + u8 eecs; + u8 eesk; + u8 eedi; + + enum adm6996_model model; + + bool enable_vlan; + bool vlan_enabled; /* Current hardware state */ + +#ifdef DEBUG + u16 addr; /* Debugging: register address to operate on */ +#endif + + u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ + u8 tagged_ports; + + u16 vlan_id[ADM_NUM_VLANS]; + u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ + u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ + + struct mutex mib_lock; + char buf[2048]; + + struct mutex reg_mutex; + + /* use abstraction for regops, we want to add gpio support in the future */ + u16 (*read)(struct adm6996_priv *priv, enum admreg reg); + void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val); +}; + +#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) +#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + +#define MIB_DESC(_o, _n) \ + { \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct adm6996_mib_desc adm6996_mibs[] = { + MIB_DESC(ADM_CL0, "RxPacket"), + MIB_DESC(ADM_CL6, "RxByte"), + MIB_DESC(ADM_CL12, "TxPacket"), + MIB_DESC(ADM_CL18, "TxByte"), + MIB_DESC(ADM_CL24, "Collision"), + MIB_DESC(ADM_CL30, "Error"), +}; + +#define ADM6996_MIB_RXB_ID 1 +#define ADM6996_MIB_TXB_ID 3 + +static inline u16 +r16(struct adm6996_priv *priv, enum admreg reg) +{ + return priv->read(priv, reg); +} + +static inline void +w16(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + priv->write(priv, reg, val); +} + +/* Minimum timing constants */ +#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */ +#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */ +#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */ + +static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + /* Bit bang from MSB to LSB */ + for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Output on rising edge */ + gpio_set_value(priv->eedi, (mask & buf[i])); + udelay(EEDI_SETUP_TIME); + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + u8 byte; + + /* Bit bang from MSB to LSB */ + for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { + u8 gp; + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Input on rising edge */ + gp = gpio_get_value(priv->eedi); + if (gp) + byte |= mask; + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + + *buf++ = byte; + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +/* Advance clock(s) */ +static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) +{ + int i; + for (i = 0; i < clocks; i++) { + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + } +} + +static u16 +adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) +{ + /* cmd: 01 10 T DD R RRRRRR */ + u8 bits[6] = { + 0xFF, 0xFF, 0xFF, 0xFF, + (0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), + ((reg&63)<<2) + }; + + u8 rbits[4]; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + adm6996_gpio_write(priv, 0, bits, 46); + gpio_direction_input(priv->eedi); + adm6996_gpio_adclk(priv, 2); + adm6996_gpio_read(priv, 0, rbits, 32); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + + /* EEPROM has 16-bit registers, but pumps out two registers in one request */ + return (reg & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); +} + +/* Write chip configuration register */ +/* Follow 93c66 timing and chip's min EEPROM timing requirement */ +static void +adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ + u8 bits[4] = { + (0x05 << 5) | (reg >> 3), + (reg << 5) | (u8)(val >> 11), + (u8)(val >> 3), + (u8)(val << 5) + }; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + /* Write cmd. Total 27 bits */ + adm6996_gpio_write(priv, 1, bits, 27); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + gpio_direction_input(priv->eedi); +} + +static u16 +adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1) + return -EINVAL; + + priv->enable_vlan = val->value.i; + + return 0; +}; + +static int +adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->enable_vlan; + + return 0; +}; + +#ifdef DEBUG + +static int +adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1023) + return -EINVAL; + + priv->addr = val->value.i; + + return 0; +}; + +static int +adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->addr; + + return 0; +}; + +static int +adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 65535) + return -EINVAL; + + w16(priv, priv->addr, val->value.i); + + return 0; +}; + +static int +adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = r16(priv, priv->addr); + + return 0; +}; + +#endif /* def DEBUG */ + +static int +adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_pvid port %d vlan %d\n", port, vlan); + + if (vlan > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->pvid[port] = vlan; + + return 0; +} + +static int +adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_pvid port %d\n", port); + *vlan = priv->pvid[port]; + + return 0; +} + +static int +adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i); + + if (val->value.i > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + + return 0; +}; + +static int +adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_vid port %d\n", val->port_vlan); + + val->value.i = priv->vlan_id[val->port_vlan]; + + return 0; +}; + +static int +adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + u8 tagged = priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("get_ports port_vlan %d\n", val->port_vlan); + + val->len = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + + return 0; +}; + +static int +adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 *ports = &priv->vlan_table[val->port_vlan]; + u8 *tagged = &priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("set_ports port_vlan %d ports", val->port_vlan); + + *ports = 0; + *tagged = 0; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + +#ifdef DEBUG + pr_cont(" %d%s", p->id, + ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : + "")); +#endif + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + *tagged |= (1 << p->id); + priv->tagged_ports |= (1 << p->id); + } + + *ports |= (1 << p->id); + } + +#ifdef DEBUG + pr_cont("\n"); +#endif + + return 0; +}; + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_enable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_OTBE_MASK); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg &= ~(ADM_IFNTE_MASK); + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg |= ADM_VID_CHECK_MASK; + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg |= ADM_NTTE; + reg &= ~(ADM_RVID1); + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + w16(priv, ADM_SYSC3, reg); +} + +static void +adm6996_enable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + reg |= ADM_MAC_CLONE; + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + reg = ADM_VLAN_FILT_MEMBER_MASK; + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg |= ADM_OTBE_MASK; + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg |= ADM_IFNTE_MASK; + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg &= ~(ADM_VID_CHECK_MASK); + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg &= ~(ADM_NTTE); + reg |= ADM_RVID1; + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + w16(priv, ADM_VLAN_MAP(i), 0); + } + + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + reg &= ~(ADM_MAC_CLONE); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_port_pvids(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = r16(priv, adm_portcfg[i]); + reg &= ~(ADM_PORTCFG_PVID_MASK); + reg |= ADM_PORTCFG_PVID(priv->pvid[i]); + if (priv->model == ADM6996L) { + if (priv->tagged_ports & (1 << i)) + reg |= (1 << 4); + else + reg &= ~(1 << 4); + } + w16(priv, adm_portcfg[i], reg); + } + + w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); + w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P2_PVID_VAL(priv->pvid[2]); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = ADM_P3_PVID_VAL(priv->pvid[3]); + reg |= ADM_P4_PVID_VAL(priv->pvid[4]); + w16(priv, ADM_P3_P4_PVID, reg); + reg = r16(priv, ADM_P5_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P5_PVID_VAL(priv->pvid[5]); + w16(priv, ADM_P5_PVID, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_vlan_filters(struct adm6996_priv *priv) +{ + u8 ports, tagged; + u16 vid, reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + vid = priv->vlan_id[i]; + ports = priv->vlan_table[i]; + tagged = priv->vlan_tagged[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_FILT_H(i), 0); + w16(priv, ADM_VLAN_FILT_L(i), 0); + continue; + } + + reg = ADM_VLAN_FILT_MEMBER(ports); + reg |= ADM_VLAN_FILT_TAGGED(tagged); + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } +} + +static void +adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) +{ + u8 ports; + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + ports = priv->vlan_table[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_MAP(i), 0); + continue; + } else { + reg = ADM_VLAN_FILT(ports); + w16(priv, ADM_VLAN_MAP(i), reg); + } + } +} + +static int +adm6996_hw_apply(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("hw_apply\n"); + + mutex_lock(&priv->reg_mutex); + + if (!priv->enable_vlan) { + if (priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_disable_vlan_6996l(priv); + else + adm6996_disable_vlan(priv); + priv->vlan_enabled = 0; + } + goto out; + } + + if (!priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_enable_vlan_6996l(priv); + else + adm6996_enable_vlan(priv); + priv->vlan_enabled = 1; + } + + adm6996_apply_port_pvids(priv); + if (priv->model == ADM6996L) + adm6996_apply_vlan_filters_6996l(priv); + else + adm6996_apply_vlan_filters(priv); + +out: + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +/* + * Reset the switch + * + * The ADM6996 can't do a software-initiated reset, so we just initialise the + * registers we support in this driver. + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_perform_reset (struct adm6996_priv *priv) +{ + int i; + + /* initialize port and vlan settings */ + for (i = 0; i < ADM_NUM_PORTS - 1; i++) { + w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT | + ADM_PORTCFG_PVID(0)); + } + w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); + + if (priv->model == ADM6996M || priv->model == ADM6996FC) { + /* reset all PHY ports */ + for (i = 0; i < ADM_PHY_PORTS; i++) { + w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); + } + } + + priv->enable_vlan = 0; + priv->vlan_enabled = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + priv->pvid[i] = 0; + } + + for (i = 0; i < ADM_NUM_VLANS; i++) { + priv->vlan_id[i] = i; + priv->vlan_table[i] = 0; + priv->vlan_tagged[i] = 0; + } + + if (priv->model == ADM6996M) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan(priv); + adm6996_apply_port_pvids(priv); + } else if (priv->model == ADM6996L) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan_6996l(priv); + adm6996_apply_port_pvids(priv); + } +} + +static int +adm6996_reset_switch(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("reset\n"); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +adm6996_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct adm6996_priv *priv = to_adm(dev); + + u16 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + switch (port) { + case 0: + reg = r16(priv, ADM_PS0); + break; + case 1: + reg = r16(priv, ADM_PS0); + reg = reg >> 8; + break; + case 2: + reg = r16(priv, ADM_PS1); + break; + case 3: + reg = r16(priv, ADM_PS1); + reg = reg >> 8; + break; + case 4: + reg = r16(priv, ADM_PS1); + reg = reg >> 12; + break; + case 5: + reg = r16(priv, ADM_PS2); + /* Bits 0, 1, 3 and 4. */ + reg = (reg & 3) | ((reg & 24) >> 1); + break; + default: + return -EINVAL; + } + + link->link = reg & ADM_PS_LS; + if (!link->link) + return 0; + link->aneg = true; + link->duplex = reg & ADM_PS_DS; + link->tx_flow = reg & ADM_PS_FCS; + link->rx_flow = reg & ADM_PS_FCS; + if (reg & ADM_PS_SS) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +adm6996_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + int port; + char *buf = priv->buf; + int i, len = 0; + u32 reg = 0; + + port = val->port_vlan; + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "Port %d MIB counters\n", + port); + + for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { + reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %u\n", + adm6996_mibs[i].name, + reg); + } + + mutex_unlock(&priv->mib_lock); + + val->value.s = buf; + val->len = len; + + return 0; +} + +static int +adm6996_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + struct adm6996_priv *priv = to_adm(dev); + int id; + u32 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + id = ADM6996_MIB_TXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->tx_bytes = reg; + + id = ADM6996_MIB_RXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->rx_bytes = reg; + + mutex_unlock(&priv->mib_lock); + + return 0; +} + +static struct switch_attr adm6996_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLANs", + .set = adm6996_set_enable_vlan, + .get = adm6996_get_enable_vlan, + }, +#ifdef DEBUG + { + .type = SWITCH_TYPE_INT, + .name = "addr", + .description = + "Direct register access: set register address (0 - 1023)", + .set = adm6996_set_addr, + .get = adm6996_get_addr, + }, + { + .type = SWITCH_TYPE_INT, + .name = "data", + .description = + "Direct register access: read/write to register (0 - 65535)", + .set = adm6996_set_data, + .get = adm6996_get_data, + }, +#endif /* def DEBUG */ +}; + +static struct switch_attr adm6996_port[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = adm6996_sw_get_port_mib, + }, +}; + +static struct switch_attr adm6996_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = adm6996_set_vid, + .get = adm6996_get_vid, + }, +}; + +static struct switch_dev_ops adm6996_ops = { + .attr_global = { + .attr = adm6996_globals, + .n_attr = ARRAY_SIZE(adm6996_globals), + }, + .attr_port = { + .attr = adm6996_port, + .n_attr = ARRAY_SIZE(adm6996_port), + }, + .attr_vlan = { + .attr = adm6996_vlan, + .n_attr = ARRAY_SIZE(adm6996_vlan), + }, + .get_port_pvid = adm6996_get_pvid, + .set_port_pvid = adm6996_set_pvid, + .get_vlan_ports = adm6996_get_ports, + .set_vlan_ports = adm6996_set_ports, + .apply_config = adm6996_hw_apply, + .reset_switch = adm6996_reset_switch, + .get_port_link = adm6996_get_port_link, + .get_port_stats = adm6996_get_port_stats, +}; + +static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) +{ + struct switch_dev *swdev; + u16 test, old; + + if (!priv->model) { + /* Detect type of chip */ + old = r16(priv, ADM_VID_CHECK); + test = old ^ (1 << 12); + w16(priv, ADM_VID_CHECK, test); + test ^= r16(priv, ADM_VID_CHECK); + if (test & (1 << 12)) { + /* + * Bit 12 of this register is read-only. + * This is the FC model. + */ + priv->model = ADM6996FC; + } else { + /* Bit 12 is read-write. This is the M model. */ + priv->model = ADM6996M; + w16(priv, ADM_VID_CHECK, old); + } + } + + swdev = &priv->dev; + swdev->name = (adm6996_model_name[priv->model]); + swdev->cpu_port = ADM_CPU_PORT; + swdev->ports = ADM_NUM_PORTS; + swdev->vlans = ADM_NUM_VLANS; + swdev->ops = &adm6996_ops; + swdev->alias = alias; + + /* The ADM6996L connected through GPIOs does not support any switch + status calls */ + if (priv->model == ADM6996L) { + adm6996_ops.attr_port.n_attr = 0; + adm6996_ops.get_port_link = NULL; + } + + pr_info ("%s: %s model PHY found.\n", alias, swdev->name); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + + if (priv->model == ADM6996M || priv->model == ADM6996L) { + return register_switch(swdev, netdev); + } + + return -ENODEV; +} + +static int adm6996_config_init(struct phy_device *pdev) +{ + struct adm6996_priv *priv; + int ret; + + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + + if (pdev->mdio.addr != 0) { + pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" + , pdev->attached_dev->name, pdev->mdio.addr); + return 0; + } + + priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + priv->priv = pdev; + priv->read = adm6996_read_mii_reg; + priv->write = adm6996_write_mii_reg; + + ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); + if (ret < 0) + return ret; + + pdev->priv = priv; + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int adm6996_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* Our custom registers are at PHY addresses 0-10. Claim those. */ + if (dev->mdio.addr > 10) + return 0; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; + if (reg != ADM_SIG0_VAL) + return 0; + + reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; + if (reg != ADM_SIG1_VAL) + return 0; + + dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; + + return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ + return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ + struct adm6996_priv *priv = phy_to_adm(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); +} + +static int adm6996_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver adm6996_phy_driver = { + .name = "Infineon ADM6996", + .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = adm6996_probe, + .remove = adm6996_remove, + .config_init = &adm6996_config_init, + .config_aneg = &adm6996_config_aneg, + .read_status = &adm6996_read_status, + .soft_reset = adm6996_soft_reset, +}; + +static int adm6996_gpio_probe(struct platform_device *pdev) +{ + struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; + struct adm6996_priv *priv; + int ret; + + if (!pdata) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + + priv->eecs = pdata->eecs; + priv->eedi = pdata->eedi; + priv->eesk = pdata->eesk; + + priv->model = pdata->model; + priv->read = adm6996_read_gpio_reg; + priv->write = adm6996_write_gpio_reg; + + ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); + if (ret) + return ret; + + ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int adm6996_gpio_remove(struct platform_device *pdev) +{ + struct adm6996_priv *priv = platform_get_drvdata(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); + + return 0; +} + +static struct platform_driver adm6996_gpio_driver = { + .probe = adm6996_gpio_probe, + .remove = adm6996_gpio_remove, + .driver = { + .name = "adm6996_gpio", + }, +}; + +static int __init adm6996_init(void) +{ + int err; + + phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); + err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); + if (err) + return err; + + err = platform_driver_register(&adm6996_gpio_driver); + if (err) + phy_driver_unregister(&adm6996_phy_driver); + + return err; +} + +static void __exit adm6996_exit(void) +{ + platform_driver_unregister(&adm6996_gpio_driver); + phy_driver_unregister(&adm6996_phy_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/adm6996.h b/target/linux/generic/files-3.18/drivers/net/phy/adm6996.h new file mode 100644 index 0000000000..6fd460a465 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/adm6996.h @@ -0,0 +1,186 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau + * Copyright (c) 2010,2011 Peter Lebbing + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +/* + * ADM_PHY_PORTS: Number of ports with a PHY. + * We only control ports 0 to 3, because if 4 is connected, it is most likely + * not connected to the switch but to a separate MII and MAC for the WAN port. + */ +#define ADM_PHY_PORTS 4 +#define ADM_NUM_PORTS 6 +#define ADM_CPU_PORT 5 + +#define ADM_NUM_VLANS 16 +#define ADM_VLAN_MAX_ID 4094 + +enum admreg { + ADM_EEPROM_BASE = 0x0, + ADM_P0_CFG = ADM_EEPROM_BASE + 1, + ADM_P1_CFG = ADM_EEPROM_BASE + 3, + ADM_P2_CFG = ADM_EEPROM_BASE + 5, + ADM_P3_CFG = ADM_EEPROM_BASE + 7, + ADM_P4_CFG = ADM_EEPROM_BASE + 8, + ADM_P5_CFG = ADM_EEPROM_BASE + 9, + ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, + ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, + ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, + /* Input Force No Tag Enable */ + ADM_IFNTE = ADM_EEPROM_BASE + 0x20, + ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, + ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, + ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, + /* Output Tag Bypass Enable and P2 PVID */ + ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, + ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, + ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, + ADM_EEPROM_EXT_BASE = 0x40, +#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) +#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) +#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n) + ADM_COUNTER_BASE = 0xa0, + ADM_SIG0 = ADM_COUNTER_BASE + 0, + ADM_SIG1 = ADM_COUNTER_BASE + 1, + ADM_PS0 = ADM_COUNTER_BASE + 2, + ADM_PS1 = ADM_COUNTER_BASE + 3, + ADM_PS2 = ADM_COUNTER_BASE + 4, + ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */ + ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */ + ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */ + ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */ + ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */ + ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */ +#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2) + ADM_PHY_BASE = 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define ADM_SIG0_MASK 0xffff +#define ADM_SIG0_VAL 0x1023 +#define ADM_SIG1_MASK 0xffff +#define ADM_SIG1_VAL 0x0007 + +enum { + ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ + ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ + ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ + ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ + ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ + ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ + ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ + ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ + ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ + ADM_PHYCFG_INIT = ( + ADM_PHYCFG_RST | + ADM_PHYCFG_SPEED_100 | + ADM_PHYCFG_ANEN | + ADM_PHYCFG_ANEN_RST + ) +}; + +enum { + ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ + ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ + ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ + ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ + ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ + ADM_PORTCFG_PD = (1 << 5), /* Port disable */ + ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority + * 1 = TOS based priority */ + ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ + ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ + ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ + ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ + ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ + + ADM_PORTCFG_INIT = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_AN | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_DPLX | + ADM_PORTCFG_CAM + ), + ADM_PORTCFG_CPU = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_OT | + ADM_PORTCFG_DPLX + ), +}; + +#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) +#define ADM_PORTCFG_PVID_MASK (0xf << 10) + +#define ADM_IFNTE_MASK (0x3f << 9) +#define ADM_VID_CHECK_MASK (0x3f << 6) + +#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) +#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_MASK 0xff + +#define ADM_OTBE(n) (((n) & 0x3f) << 8) +#define ADM_OTBE_MASK (0x3f << 8) + +/* ADM_SYSC0 */ +enum { + ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ + ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ +}; + +/* Tag Based VLAN in ADM_SYSC3 */ +#define ADM_MAC_CLONE BIT(4) +#define ADM_TBV BIT(5) + +static const u8 adm_portcfg[] = { + [0] = ADM_P0_CFG, + [1] = ADM_P1_CFG, + [2] = ADM_P2_CFG, + [3] = ADM_P3_CFG, + [4] = ADM_P4_CFG, + [5] = ADM_P5_CFG, +}; + +/* Fields in ADM_VLAN_FILT_L(x) */ +#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) +#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) +#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) +#define ADM_VLAN_FILT_MEMBER_MASK 0x3f +/* Fields in ADM_VLAN_FILT_H(x) */ +#define ADM_VLAN_FILT_VALID (1 << 15) +#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) + +/* Convert ports to a form for ADM6996L VLAN map */ +#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \ + ((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \ + ((ports & 0x10) << 3) | ((ports & 0x20) << 3)) + +/* Port status register */ +enum { + ADM_PS_LS = (1 << 0), /* Link status */ + ADM_PS_SS = (1 << 1), /* Speed status */ + ADM_PS_DS = (1 << 2), /* Duplex status */ + ADM_PS_FCS = (1 << 3) /* Flow control status */ +}; + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/ar8216.c b/target/linux/generic/files-3.18/drivers/net/phy/ar8216.c new file mode 100644 index 0000000000..7512ee1b43 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/ar8216.c @@ -0,0 +1,2313 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" + +extern const struct ar8xxx_chip ar8327_chip; +extern const struct ar8xxx_chip ar8337_chip; + +#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ + +#define MIB_DESC(_s , _o, _n) \ + { \ + .size = (_s), \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct ar8xxx_mib_desc ar8216_mibs[] = { + MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), +}; + +const struct ar8xxx_mib_desc ar8236_mibs[39] = { + MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), + MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), + MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), +}; + +static DEFINE_MUTEX(ar8xxx_dev_list_lock); +static LIST_HEAD(ar8xxx_dev_list); + +/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ +static int +ar8xxx_phy_poll_reset(struct mii_bus *bus) +{ + unsigned int sleep_msecs = 20; + int ret, elapsed, i; + + for (elapsed = sleep_msecs; elapsed <= 600; + elapsed += sleep_msecs) { + msleep(sleep_msecs); + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + ret = mdiobus_read(bus, i, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_RESET) + break; + if (i == AR8XXX_NUM_PHYS - 1) { + usleep_range(1000, 2000); + return 0; + } + } + } + return -ETIMEDOUT; +} + +static int +ar8xxx_phy_check_aneg(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg != AUTONEG_ENABLE) + return 0; + /* + * BMCR_ANENABLE might have been cleared + * by phy_init_hw in certain kernel versions + * therefore check for it + */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_ANENABLE) + return 0; + + dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); + ret |= BMCR_ANENABLE | BMCR_ANRESTART; + return phy_write(phydev, MII_BMCR, ret); +} + +void +ar8xxx_phy_init(struct ar8xxx_priv *priv) +{ + int i; + struct mii_bus *bus; + + bus = priv->mii_bus; + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + if (priv->chip->phy_fixup) + priv->chip->phy_fixup(priv, i); + + /* initialize the port itself */ + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + if (ar8xxx_has_gige(priv)) + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + + ar8xxx_phy_poll_reset(bus); +} + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = bus->read(bus, phy_id, regnum); + hi = bus->read(bus, phy_id, regnum + 1); + + return (hi << 16) | lo; +} + +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = val & 0xffff; + hi = (u16) (val >> 16); + + if (priv->chip->mii_lo_first) + { + bus->write(bus, phy_id, regnum, lo); + bus->write(bus, phy_id, regnum + 1, hi); + } else { + bus->write(bus, phy_id, regnum + 1, hi); + bus->write(bus, phy_id, regnum, lo); + } +} + +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 val; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + + mutex_unlock(&bus->mdio_lock); + + return val; +} + +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); + + mutex_unlock(&bus->mdio_lock); +} + +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 ret; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + ret &= ~mask; + ret |= val; + ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); + + mutex_unlock(&bus->mdio_lock); + + return ret; +} + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); + bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); + mutex_unlock(&bus->mdio_lock); +} + +static inline void +ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) +{ + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); +} + +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); +} + +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 data; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); + mutex_unlock(&bus->mdio_lock); + + return data; +} + +static int +ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + u32 t; + + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + usleep_range(1000, 2000); + cond_resched(); + } + + return -ETIMEDOUT; +} + +static int +ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) +{ + unsigned mib_func = priv->chip->mib_func; + int ret; + + lockdep_assert_held(&priv->mib_lock); + + /* Capture the hardware statistics for all ports */ + ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); + + /* Wait for the capturing to complete. */ + ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); + if (ret) + goto out; + + ret = 0; + +out: + return ret; +} + +static int +ar8xxx_mib_capture(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); +} + +static int +ar8xxx_mib_flush(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); +} + +static void +ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) +{ + unsigned int base; + u64 *mib_stats; + int i; + + WARN_ON(port >= priv->dev.ports); + + lockdep_assert_held(&priv->mib_lock); + + base = priv->chip->reg_port_stats_start + + priv->chip->reg_port_stats_length * port; + + mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; + for (i = 0; i < priv->chip->num_mibs; i++) { + const struct ar8xxx_mib_desc *mib; + u64 t; + + mib = &priv->chip->mib_decs[i]; + t = ar8xxx_read(priv, base + mib->offset); + if (mib->size == 2) { + u64 hi; + + hi = ar8xxx_read(priv, base + mib->offset + 4); + t |= hi << 32; + } + + if (flush) + mib_stats[i] = 0; + else + mib_stats[i] += t; + cond_resched(); + } +} + +static void +ar8216_read_port_link(struct ar8xxx_priv *priv, int port, + struct switch_port_link *link) +{ + u32 status; + u32 speed; + + memset(link, '\0', sizeof(*link)); + + status = priv->chip->read_port_status(priv, port); + + link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); + } else { + link->link = true; + + if (priv->get_port_link) { + int err; + + err = priv->get_port_link(port); + if (err >= 0) + link->link = !!err; + } + } + + if (!link->link) + return; + + link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); + + if (link->aneg && link->duplex && priv->chip->read_port_eee_status) + link->eee = priv->chip->read_port_eee_status(priv, port); + + speed = (status & AR8216_PORT_STATUS_SPEED) >> + AR8216_PORT_STATUS_SPEED_S; + + switch (speed) { + case AR8216_PORT_SPEED_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR8216_PORT_SPEED_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR8216_PORT_SPEED_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } +} + +static struct sk_buff * +ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv = dev->phy_ptr; + unsigned char *buf; + + if (unlikely(!priv)) + goto error; + + if (!priv->vlan) + goto send; + + if (unlikely(skb_headroom(skb) < 2)) { + if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) + goto error; + } + + buf = skb_push(skb, 2); + buf[0] = 0x10; + buf[1] = 0x80; + +send: + return skb; + +error: + dev_kfree_skb_any(skb); + return NULL; +} + +static void +ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv; + unsigned char *buf; + int port, vlan; + + priv = dev->phy_ptr; + if (!priv) + return; + + /* don't strip the header if vlan mode is disabled */ + if (!priv->vlan) + return; + + /* strip header, get vlan id */ + buf = skb->data; + skb_pull(skb, 2); + + /* check for vlan header presence */ + if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) + return; + + port = buf[0] & 0x7; + + /* no need to fix up packets coming from a tagged source */ + if (priv->vlan_tagged & (1 << port)) + return; + + /* lookup port vid from local table, the switch passes an invalid vlan id */ + vlan = priv->vlan_id[priv->pvid[port]]; + + buf[14 + 2] &= 0xf0; + buf[14 + 2] |= vlan >> 8; + buf[15 + 2] = vlan & 0xff; +} + +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + int timeout = 20; + u32 t = 0; + + while (1) { + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + if (timeout-- <= 0) + break; + + udelay(10); + cond_resched(); + } + + pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", + (unsigned int) reg, t, mask, val); + return -ETIMEDOUT; +} + +static void +ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) + return; + if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { + val &= AR8216_VTUDATA_MEMBER; + val |= AR8216_VTUDATA_VALID; + ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); + } + op |= AR8216_VTU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_VTU, op); +} + +static void +ar8216_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); +} + +static void +ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + + op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); + ar8216_vtu_op(priv, op, port_mask); +} + +static int +ar8216_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | + AR8216_ATU_ACTIVE); + + return ret; +} + +static int +ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) { + t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; + t |= AR8216_ATU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); + } + + return ret; +} + +static u32 +ar8216_read_port_status(struct ar8xxx_priv *priv, int port) +{ + return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); +} + +static void +ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 header; + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) + header = AR8216_PORT_CTRL_HEADER; + else + header = 0; + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | header | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), + AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | + AR8216_PORT_VLAN_DEFAULT_ID, + (members << AR8216_PORT_VLAN_DEST_PORTS_S) | + (ingress << AR8216_PORT_VLAN_MODE_S) | + (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +} + +static int +ar8216_hw_init(struct ar8xxx_priv *priv) +{ + if (priv->initialized) + return 0; + + ar8xxx_phy_init(priv); + + priv->initialized = true; + return 0; +} + +static void +ar8216_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8216_GCTRL_MTU, 1518 + 8 + 2); +} + +static void +ar8216_init_port(struct ar8xxx_priv *priv, int port) +{ + /* Enable port learning and tx */ + ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | + (4 << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); + + if (port == AR8216_PORT_CPU) { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_UP | + (ar8xxx_has_gige(priv) ? + AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | + AR8216_PORT_STATUS_TXMAC | + AR8216_PORT_STATUS_RXMAC | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | + AR8216_PORT_STATUS_DUPLEX); + } else { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_AUTO); + } +} + +static void +ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8216: timeout waiting for atu to become ready\n"); +} + +static void ar8216_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_func0, r1_func1, r1_func2; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); + r2 |= 0x10; + + r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; + r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8216_wait_atu_ready(priv, r2, r1_func0); + + ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); + ar8xxx_mii_write32(priv, r2, r1_func1, 0); + ar8xxx_mii_write32(priv, r2, r1_func2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + t = ar8xxx_mii_read32(priv, r2, r1_func0); + t |= AR8216_ATU_ACTIVE; + ar8xxx_mii_write32(priv, r2, r1_func0, t); + ar8216_wait_atu_ready(priv, r2, r1_func0); + + val0 = ar8xxx_mii_read32(priv, r2, r1_func0); + val1 = ar8xxx_mii_read32(priv, r2, r1_func1); + val2 = ar8xxx_mii_read32(priv, r2, r1_func2); + + *status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; + if (!*status) + break; + + i = 0; + t = AR8216_ATU_PORT0; + while (!(val2 & t) && ++i < priv->dev.ports) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; + a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; + a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; + a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; + a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; + a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; + break; + } +} + +static void +ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), + AR8236_PORT_VLAN_DEFAULT_ID, + (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), + AR8236_PORT_VLAN2_VLAN_MODE | + AR8236_PORT_VLAN2_MEMBER, + (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | + (members << AR8236_PORT_VLAN2_MEMBER_S)); +} + +static void +ar8236_init_globals(struct ar8xxx_priv *priv) +{ + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* enable cpu port to receive arp frames */ + ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, + AR8236_ATU_CTRL_RES); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, + AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +static int +ar8316_hw_init(struct ar8xxx_priv *priv) +{ + u32 val, newval; + + val = ar8xxx_read(priv, AR8316_REG_POSTRIP); + + if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + if (priv->port4_phy) { + /* value taken from Ubiquiti RouterStation Pro */ + newval = 0x81461bea; + pr_info("ar8316: Using port 4 as PHY\n"); + } else { + newval = 0x01261be2; + pr_info("ar8316: Using port 4 as switch port\n"); + } + } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { + /* value taken from AVM Fritz!Box 7390 sources */ + newval = 0x010e5b71; + } else { + /* no known value for phy interface */ + pr_err("ar8316: unsupported mii mode: %d.\n", + priv->phy->interface); + return -EINVAL; + } + + if (val == newval) + goto out; + + ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); + + if (priv->port4_phy && + priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + /* work around for phy4 rgmii mode */ + ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); + /* rx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); + /* tx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); + msleep(1000); + } + + ar8xxx_phy_init(priv); + +out: + priv->initialized = true; + return 0; +} + +static void +ar8316_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + priv->vlan = !!val->value.i; + return 0; +} + +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan; + return 0; +} + + +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan < 0 || vlan >= dev->vlans || + port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + *vlan = priv->pvid[port]; + return 0; +} + +static int +ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + ar8216_read_port_link(priv, port, link); + return 0; +} + +static int +ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports; + int i; + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + ports = priv->vlan_table[val->port_vlan]; + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + priv->vlan_tagged |= (1 << p->id); + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8216_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + for (port = 0; port < AR8216_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_RX); + + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_TX); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8216_NUM_PORTS || + priv->monitor_port >= AR8216_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_RX); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_TX); +} + +static inline u32 +ar8xxx_age_time_val(int age_time) +{ + return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; +} + +static inline void +ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) +{ + u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); + ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); +} + +int +ar8xxx_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u8 portmask[AR8X16_MAX_PORTS]; + int i, j; + + mutex_lock(&priv->reg_mutex); + /* flush all vlan translation unit entries */ + priv->chip->vtu_flush(priv); + + memset(portmask, 0, sizeof(portmask)); + if (!priv->init) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < dev->ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + chip->vtu_load_vlan(priv, priv->vlan_id[j], + priv->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < dev->ports; i++) { + if (i == AR8216_PORT_CPU) + continue; + + portmask[i] = 1 << AR8216_PORT_CPU; + portmask[AR8216_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < dev->ports; i++) { + chip->setup_port(priv, i, portmask[i]); + } + + chip->set_mirror_regs(priv); + + /* set age time */ + if (chip->reg_arl_ctrl) + ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +int +ar8xxx_sw_reset_switch(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + int i; + + mutex_lock(&priv->reg_mutex); + memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - + offsetof(struct ar8xxx_priv, vlan)); + + for (i = 0; i < AR8X16_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + /* Configure all ports */ + for (i = 0; i < dev->ports; i++) + chip->init_port(priv, i); + + priv->mirror_rx = false; + priv->mirror_tx = false; + priv->source_port = 0; + priv->monitor_port = 0; + priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; + + chip->init_globals(priv); + chip->atu_flush(priv); + + mutex_unlock(&priv->reg_mutex); + + return chip->sw_hw_apply(dev); +} + +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + unsigned int len; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + mutex_lock(&priv->mib_lock); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + memset(priv->mib_stats, '\0', len); + ret = ar8xxx_mib_flush(priv); + if (ret) + goto unlock; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_rx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_rx; + return 0; +} + +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_tx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_tx; + return 0; +} + +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->monitor_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->monitor_port; + return 0; +} + +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->source_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->source_port; + return 0; +} + +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, true); + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +static void +ar8xxx_byte_to_str(char *buf, int len, u64 byte) +{ + unsigned long b; + const char *unit; + + if (byte >= 0x40000000) { /* 1 GiB */ + b = byte * 10 / 0x40000000; + unit = "GiB"; + } else if (byte >= 0x100000) { /* 1 MiB */ + b = byte * 10 / 0x100000; + unit = "MiB"; + } else if (byte >= 0x400) { /* 1 KiB */ + b = byte * 10 / 0x400; + unit = "KiB"; + } else { + b = byte; + unit = "Byte"; + } + if (strcmp(unit, "Byte")) + snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); + else + snprintf(buf, len, "%lu %s", b, unit); +} + +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u64 *mib_stats, mib_data; + unsigned int port; + int ret; + char *buf = priv->buf; + char buf1[64]; + const char *mib_name; + int i, len = 0; + bool mib_stats_empty = true; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, false); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "MIB counters\n"); + + mib_stats = &priv->mib_stats[port * chip->num_mibs]; + for (i = 0; i < chip->num_mibs; i++) { + mib_name = chip->mib_decs[i].name; + mib_data = mib_stats[i]; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %llu\n", mib_name, mib_data); + if ((!strcmp(mib_name, "TxByte") || + !strcmp(mib_name, "RxGoodByte")) && + mib_data >= 1024) { + ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); + --len; /* discard newline at the end of buf */ + len += snprintf(buf + len, sizeof(priv->buf) - len, + " (%s)\n", buf1); + } + if (mib_stats_empty && mib_data) + mib_stats_empty = false; + } + + if (mib_stats_empty) + len = snprintf(buf, sizeof(priv->buf), "No MIB data"); + + val->value.s = buf; + val->len = len; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int age_time = val->value.i; + u32 age_time_val; + + if (age_time < 0) + return -EINVAL; + + age_time_val = ar8xxx_age_time_val(age_time); + if (age_time_val == 0 || age_time_val > 0xffff) + return -EINVAL; + + priv->arl_age_time = age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->arl_age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct mii_bus *bus = priv->mii_bus; + const struct ar8xxx_chip *chip = priv->chip; + char *buf = priv->arl_buf; + int i, j, k, len = 0; + struct arl_entry *a, *a1; + u32 status; + + if (!chip->get_arl_entry) + return -EOPNOTSUPP; + + mutex_lock(&priv->reg_mutex); + mutex_lock(&bus->mdio_lock); + + chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); + + for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { + a = &priv->arl_table[i]; + duplicate: + chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); + + if (!status) + break; + + /* avoid duplicates + * ARL table can include multiple valid entries + * per MAC, just with differing status codes + */ + for (j = 0; j < i; ++j) { + a1 = &priv->arl_table[j]; + if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) + goto duplicate; + } + } + + mutex_unlock(&bus->mdio_lock); + + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "address resolution table\n"); + + if (i == AR8XXX_NUM_ARL_RECORDS) + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Too many entries found, displaying the first %d only!\n", + AR8XXX_NUM_ARL_RECORDS); + + for (j = 0; j < priv->dev.ports; ++j) { + for (k = 0; k < i; ++k) { + a = &priv->arl_table[k]; + if (a->port != j) + continue; + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + j, + a->mac[5], a->mac[4], a->mac[3], + a->mac[2], a->mac[1], a->mac[0]); + } + } + + val->value.s = buf; + val->len = len; + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int ret; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush(priv); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port, ret; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush_port(priv, port); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static const struct switch_attr ar8xxx_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_vlan[1] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = ar8xxx_sw_set_vid, + .get = ar8xxx_sw_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar8xxx_sw_ops = { + .attr_global = { + .attr = ar8xxx_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), + }, + .attr_port = { + .attr = ar8xxx_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8xxx_sw_get_ports, + .set_vlan_ports = ar8xxx_sw_set_ports, + .apply_config = ar8xxx_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +static const struct ar8xxx_chip ar8216_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x19000, + .reg_port_stats_length = 0xa0, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8216", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8216_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8216_mibs), + .mib_decs = ar8216_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8236_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8236", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8236_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8236_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8316_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8316", + .ports = AR8216_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8316_hw_init, + .init_globals = ar8316_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static int +ar8xxx_id_chip(struct ar8xxx_priv *priv) +{ + u32 val; + u16 id; + int i; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { + u16 t; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + if (t != id) + return -ENODEV; + } + + priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; + priv->chip_rev = (id & AR8216_CTRL_REVISION); + + switch (priv->chip_ver) { + case AR8XXX_VER_AR8216: + priv->chip = &ar8216_chip; + break; + case AR8XXX_VER_AR8236: + priv->chip = &ar8236_chip; + break; + case AR8XXX_VER_AR8316: + priv->chip = &ar8316_chip; + break; + case AR8XXX_VER_AR8327: + priv->chip = &ar8327_chip; + break; + case AR8XXX_VER_AR8337: + priv->chip = &ar8337_chip; + break; + default: + pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", + priv->chip_ver, priv->chip_rev); + + return -ENODEV; + } + + return 0; +} + +static void +ar8xxx_mib_work_func(struct work_struct *work) +{ + struct ar8xxx_priv *priv; + int err; + + priv = container_of(work, struct ar8xxx_priv, mib_work.work); + + mutex_lock(&priv->mib_lock); + + err = ar8xxx_mib_capture(priv); + if (err) + goto next_port; + + ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); + +next_port: + priv->mib_next_port++; + if (priv->mib_next_port >= priv->dev.ports) + priv->mib_next_port = 0; + + mutex_unlock(&priv->mib_lock); + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static int +ar8xxx_mib_init(struct ar8xxx_priv *priv) +{ + unsigned int len; + + if (!ar8xxx_has_mib_counters(priv)) + return 0; + + BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + priv->mib_stats = kzalloc(len, GFP_KERNEL); + + if (!priv->mib_stats) + return -ENOMEM; + + return 0; +} + +static void +ar8xxx_mib_start(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static void +ar8xxx_mib_stop(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + cancel_delayed_work_sync(&priv->mib_work); +} + +static struct ar8xxx_priv * +ar8xxx_create(void) +{ + struct ar8xxx_priv *priv; + + priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); + + return priv; +} + +static void +ar8xxx_free(struct ar8xxx_priv *priv) +{ + if (priv->chip && priv->chip->cleanup) + priv->chip->cleanup(priv); + + kfree(priv->chip_data); + kfree(priv->mib_stats); + kfree(priv); +} + +static int +ar8xxx_probe_switch(struct ar8xxx_priv *priv) +{ + const struct ar8xxx_chip *chip; + struct switch_dev *swdev; + int ret; + + ret = ar8xxx_id_chip(priv); + if (ret) + return ret; + + chip = priv->chip; + + swdev = &priv->dev; + swdev->cpu_port = AR8216_PORT_CPU; + swdev->name = chip->name; + swdev->vlans = chip->vlans; + swdev->ports = chip->ports; + swdev->ops = chip->swops; + + ret = ar8xxx_mib_init(priv); + if (ret) + return ret; + + return 0; +} + +static int +ar8xxx_start(struct ar8xxx_priv *priv) +{ + int ret; + + priv->init = true; + + ret = priv->chip->hw_init(priv); + if (ret) + return ret; + + ret = ar8xxx_sw_reset_switch(&priv->dev); + if (ret) + return ret; + + priv->init = false; + + ar8xxx_mib_start(priv); + + return 0; +} + +static int +ar8xxx_phy_config_init(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct net_device *dev = phydev->attached_dev; + int ret; + + if (WARN_ON(!priv)) + return -ENODEV; + + if (priv->chip->config_at_probe) + return ar8xxx_phy_check_aneg(phydev); + + priv->phy = phydev; + + if (phydev->mdio.addr != 0) { + if (chip_is_ar8316(priv)) { + /* switch device has been initialized, reinit */ + priv->dev.ports = (AR8216_NUM_PORTS - 1); + priv->initialized = false; + priv->port4_phy = true; + ar8316_hw_init(priv); + return 0; + } + + return 0; + } + + ret = ar8xxx_start(priv); + if (ret) + return ret; + + /* VID fixup only needed on ar8216 */ + if (chip_is_ar8216(priv)) { + dev->phy_ptr = priv; + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = ar8216_mangle_rx; + dev->eth_mangle_tx = ar8216_mangle_tx; + } + + return 0; +} + +static bool +ar8xxx_check_link_states(struct ar8xxx_priv *priv) +{ + bool link_new, changed = false; + u32 status; + int i; + + mutex_lock(&priv->reg_mutex); + + for (i = 0; i < priv->dev.ports; i++) { + status = priv->chip->read_port_status(priv, i); + link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); + if (link_new == priv->link_up[i]) + continue; + + priv->link_up[i] = link_new; + changed = true; + /* flush ARL entries for this port if it went down*/ + if (!link_new) + priv->chip->atu_flush_port(priv, i); + dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", + i, link_new ? "up" : "down"); + } + + mutex_unlock(&priv->reg_mutex); + + return changed; +} + +static int +ar8xxx_phy_read_status(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct switch_port_link link; + + /* check for switch port link changes */ + if (phydev->state == PHY_CHANGELINK) + ar8xxx_check_link_states(priv); + + if (phydev->mdio.addr != 0) + return genphy_read_status(phydev); + + ar8216_read_port_link(priv, phydev->mdio.addr, &link); + phydev->link = !!link.link; + if (!phydev->link) + return 0; + + switch (link.speed) { + case SWITCH_PORT_SPEED_10: + phydev->speed = SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + phydev->speed = SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + default: + phydev->speed = 0; + } + phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int +ar8xxx_phy_config_aneg(struct phy_device *phydev) +{ + if (phydev->mdio.addr == 0) + return 0; + + return genphy_config_aneg(phydev); +} + +static const u32 ar8xxx_phy_ids[] = { + 0x004dd033, + 0x004dd034, /* AR8327 */ + 0x004dd036, /* AR8337 */ + 0x004dd041, + 0x004dd042, + 0x004dd043, /* AR8236 */ +}; + +static bool +ar8xxx_phy_match(u32 phy_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) + if (phy_id == ar8xxx_phy_ids[i]) + return true; + + return false; +} + +static bool +ar8xxx_is_possible(struct mii_bus *bus) +{ + unsigned int i, found_phys = 0; + + for (i = 0; i < 5; i++) { + u32 phy_id; + + phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; + phy_id |= mdiobus_read(bus, i, MII_PHYSID2); + if (ar8xxx_phy_match(phy_id)) { + found_phys++; + } else if (phy_id) { + pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", + dev_name(&bus->dev), i, phy_id); + } + } + return !!found_phys; +} + +static int +ar8xxx_phy_probe(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv; + struct switch_dev *swdev; + int ret; + + /* skip PHYs at unused adresses */ + if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4) + return -ENODEV; + + if (!ar8xxx_is_possible(phydev->mdio.bus)) + return -ENODEV; + + mutex_lock(&ar8xxx_dev_list_lock); + list_for_each_entry(priv, &ar8xxx_dev_list, list) + if (priv->mii_bus == phydev->mdio.bus) + goto found; + + priv = ar8xxx_create(); + if (priv == NULL) { + ret = -ENOMEM; + goto unlock; + } + + priv->mii_bus = phydev->mdio.bus; + + ret = ar8xxx_probe_switch(priv); + if (ret) + goto free_priv; + + swdev = &priv->dev; + swdev->alias = dev_name(&priv->mii_bus->dev); + ret = register_switch(swdev, NULL); + if (ret) + goto free_priv; + + pr_info("%s: %s rev. %u switch registered on %s\n", + swdev->devname, swdev->name, priv->chip_rev, + dev_name(&priv->mii_bus->dev)); + + list_add(&priv->list, &ar8xxx_dev_list); + +found: + priv->use_count++; + + if (phydev->mdio.addr == 0) { + if (ar8xxx_has_gige(priv)) { + phydev->supported = SUPPORTED_1000baseT_Full; + phydev->advertising = ADVERTISED_1000baseT_Full; + } else { + phydev->supported = SUPPORTED_100baseT_Full; + phydev->advertising = ADVERTISED_100baseT_Full; + } + + if (priv->chip->config_at_probe) { + priv->phy = phydev; + + ret = ar8xxx_start(priv); + if (ret) + goto err_unregister_switch; + } + } else { + if (ar8xxx_has_gige(priv)) { + phydev->supported |= SUPPORTED_1000baseT_Full; + phydev->advertising |= ADVERTISED_1000baseT_Full; + } + } + + phydev->priv = priv; + + mutex_unlock(&ar8xxx_dev_list_lock); + + return 0; + +err_unregister_switch: + if (--priv->use_count) + goto unlock; + + unregister_switch(&priv->dev); + +free_priv: + ar8xxx_free(priv); +unlock: + mutex_unlock(&ar8xxx_dev_list_lock); + return ret; +} + +static void +ar8xxx_phy_detach(struct phy_device *phydev) +{ + struct net_device *dev = phydev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; +} + +static void +ar8xxx_phy_remove(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + + if (WARN_ON(!priv)) + return; + + phydev->priv = NULL; + + mutex_lock(&ar8xxx_dev_list_lock); + + if (--priv->use_count > 0) { + mutex_unlock(&ar8xxx_dev_list_lock); + return; + } + + list_del(&priv->list); + mutex_unlock(&ar8xxx_dev_list_lock); + + unregister_switch(&priv->dev); + ar8xxx_mib_stop(priv); + ar8xxx_free(priv); +} + +static int +ar8xxx_phy_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver ar8xxx_phy_driver[] = { + { + .phy_id = 0x004d0000, + .name = "Atheros AR8216/AR8236/AR8316", + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = ar8xxx_phy_probe, + .remove = ar8xxx_phy_remove, + .detach = ar8xxx_phy_detach, + .config_init = ar8xxx_phy_config_init, + .config_aneg = ar8xxx_phy_config_aneg, + .read_status = ar8xxx_phy_read_status, + .soft_reset = ar8xxx_phy_soft_reset, + } +}; + +module_phy_driver(ar8xxx_phy_driver); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/ar8216.h b/target/linux/generic/files-3.18/drivers/net/phy/ar8216.h new file mode 100644 index 0000000000..ba0e0ddccd --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/ar8216.h @@ -0,0 +1,644 @@ +/* + * ar8216.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8216_H +#define __AR8216_H + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) + +#define AR8XXX_CAP_GIGE BIT(0) +#define AR8XXX_CAP_MIB_COUNTERS BIT(1) + +#define AR8XXX_NUM_PHYS 5 +#define AR8216_PORT_CPU 0 +#define AR8216_NUM_PORTS 6 +#define AR8216_NUM_VLANS 16 +#define AR8316_NUM_VLANS 4096 + +/* size of the vlan table */ +#define AR8X16_MAX_VLANS 128 +#define AR8X16_PROBE_RETRIES 10 +#define AR8X16_MAX_PORTS 8 + +#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7 +#define AR8XXX_DEFAULT_ARL_AGE_TIME 300 + +/* Atheros specific MII registers */ +#define MII_ATH_MMD_ADDR 0x0d +#define MII_ATH_MMD_DATA 0x0e +#define MII_ATH_DBG_ADDR 0x1d +#define MII_ATH_DBG_DATA 0x1e + +#define AR8216_REG_CTRL 0x0000 +#define AR8216_CTRL_REVISION BITS(0, 8) +#define AR8216_CTRL_REVISION_S 0 +#define AR8216_CTRL_VERSION BITS(8, 8) +#define AR8216_CTRL_VERSION_S 8 +#define AR8216_CTRL_RESET BIT(31) + +#define AR8216_REG_FLOOD_MASK 0x002C +#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) +#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) +#define AR8236_FM_CPU_BROADCAST_EN BIT(26) +#define AR8236_FM_CPU_BCAST_FWD_EN BIT(25) + +#define AR8216_REG_GLOBAL_CTRL 0x0030 +#define AR8216_GCTRL_MTU BITS(0, 11) +#define AR8236_GCTRL_MTU BITS(0, 14) +#define AR8316_GCTRL_MTU BITS(0, 14) + +#define AR8216_REG_VTU 0x0040 +#define AR8216_VTU_OP BITS(0, 3) +#define AR8216_VTU_OP_NOOP 0x0 +#define AR8216_VTU_OP_FLUSH 0x1 +#define AR8216_VTU_OP_LOAD 0x2 +#define AR8216_VTU_OP_PURGE 0x3 +#define AR8216_VTU_OP_REMOVE_PORT 0x4 +#define AR8216_VTU_ACTIVE BIT(3) +#define AR8216_VTU_FULL BIT(4) +#define AR8216_VTU_PORT BITS(8, 4) +#define AR8216_VTU_PORT_S 8 +#define AR8216_VTU_VID BITS(16, 12) +#define AR8216_VTU_VID_S 16 +#define AR8216_VTU_PRIO BITS(28, 3) +#define AR8216_VTU_PRIO_S 28 +#define AR8216_VTU_PRIO_EN BIT(31) + +#define AR8216_REG_VTU_DATA 0x0044 +#define AR8216_VTUDATA_MEMBER BITS(0, 10) +#define AR8236_VTUDATA_MEMBER BITS(0, 7) +#define AR8216_VTUDATA_VALID BIT(11) + +#define AR8216_REG_ATU_FUNC0 0x0050 +#define AR8216_ATU_OP BITS(0, 3) +#define AR8216_ATU_OP_NOOP 0x0 +#define AR8216_ATU_OP_FLUSH 0x1 +#define AR8216_ATU_OP_LOAD 0x2 +#define AR8216_ATU_OP_PURGE 0x3 +#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4 +#define AR8216_ATU_OP_FLUSH_PORT 0x5 +#define AR8216_ATU_OP_GET_NEXT 0x6 +#define AR8216_ATU_ACTIVE BIT(3) +#define AR8216_ATU_PORT_NUM BITS(8, 4) +#define AR8216_ATU_PORT_NUM_S 8 +#define AR8216_ATU_FULL_VIO BIT(12) +#define AR8216_ATU_ADDR5 BITS(16, 8) +#define AR8216_ATU_ADDR5_S 16 +#define AR8216_ATU_ADDR4 BITS(24, 8) +#define AR8216_ATU_ADDR4_S 24 + +#define AR8216_REG_ATU_FUNC1 0x0054 +#define AR8216_ATU_ADDR3 BITS(0, 8) +#define AR8216_ATU_ADDR3_S 0 +#define AR8216_ATU_ADDR2 BITS(8, 8) +#define AR8216_ATU_ADDR2_S 8 +#define AR8216_ATU_ADDR1 BITS(16, 8) +#define AR8216_ATU_ADDR1_S 16 +#define AR8216_ATU_ADDR0 BITS(24, 8) +#define AR8216_ATU_ADDR0_S 24 + +#define AR8216_REG_ATU_FUNC2 0x0058 +#define AR8216_ATU_PORTS BITS(0, 6) +#define AR8216_ATU_PORT0 BIT(0) +#define AR8216_ATU_PORT1 BIT(1) +#define AR8216_ATU_PORT2 BIT(2) +#define AR8216_ATU_PORT3 BIT(3) +#define AR8216_ATU_PORT4 BIT(4) +#define AR8216_ATU_PORT5 BIT(5) +#define AR8216_ATU_STATUS BITS(16, 4) +#define AR8216_ATU_STATUS_S 16 + +#define AR8216_REG_ATU_CTRL 0x005C +#define AR8216_ATU_CTRL_AGE_EN BIT(17) +#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) +#define AR8216_ATU_CTRL_AGE_TIME_S 0 +#define AR8236_ATU_CTRL_RES BIT(20) + +#define AR8216_REG_MIB_FUNC 0x0080 +#define AR8216_MIB_TIMER BITS(0, 16) +#define AR8216_MIB_AT_HALF_EN BIT(16) +#define AR8216_MIB_BUSY BIT(17) +#define AR8216_MIB_FUNC BITS(24, 3) +#define AR8216_MIB_FUNC_S 24 +#define AR8216_MIB_FUNC_NO_OP 0x0 +#define AR8216_MIB_FUNC_FLUSH 0x1 +#define AR8216_MIB_FUNC_CAPTURE 0x3 +#define AR8236_MIB_EN BIT(30) + +#define AR8216_REG_GLOBAL_CPUPORT 0x0078 +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 + +#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) +#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) +#define AR8216_PORT_STATUS_SPEED BITS(0,2) +#define AR8216_PORT_STATUS_SPEED_S 0 +#define AR8216_PORT_STATUS_TXMAC BIT(2) +#define AR8216_PORT_STATUS_RXMAC BIT(3) +#define AR8216_PORT_STATUS_TXFLOW BIT(4) +#define AR8216_PORT_STATUS_RXFLOW BIT(5) +#define AR8216_PORT_STATUS_DUPLEX BIT(6) +#define AR8216_PORT_STATUS_LINK_UP BIT(8) +#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) +#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) +#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12) + +#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) + +/* port forwarding state */ +#define AR8216_PORT_CTRL_STATE BITS(0, 3) +#define AR8216_PORT_CTRL_STATE_S 0 + +#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) + +/* egress 802.1q mode */ +#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) +#define AR8216_PORT_CTRL_VLAN_MODE_S 8 + +#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR8216_PORT_CTRL_HEADER BIT(11) +#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) +#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR8216_PORT_CTRL_LEARN BIT(14) +#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) +#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) + +#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) +#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 + +#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) +#define AR8216_PORT_VLAN_DEST_PORTS_S 16 + +/* bit0 added to the priority field of egress frames */ +#define AR8216_PORT_VLAN_TX_PRIO BIT(27) + +/* port default priority */ +#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) +#define AR8216_PORT_VLAN_PRIORITY_S 28 + +/* ingress 802.1q mode */ +#define AR8216_PORT_VLAN_MODE BITS(30, 2) +#define AR8216_PORT_VLAN_MODE_S 30 + +#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) +#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) + +#define AR8216_STATS_RXBROAD 0x00 +#define AR8216_STATS_RXPAUSE 0x04 +#define AR8216_STATS_RXMULTI 0x08 +#define AR8216_STATS_RXFCSERR 0x0c +#define AR8216_STATS_RXALIGNERR 0x10 +#define AR8216_STATS_RXRUNT 0x14 +#define AR8216_STATS_RXFRAGMENT 0x18 +#define AR8216_STATS_RX64BYTE 0x1c +#define AR8216_STATS_RX128BYTE 0x20 +#define AR8216_STATS_RX256BYTE 0x24 +#define AR8216_STATS_RX512BYTE 0x28 +#define AR8216_STATS_RX1024BYTE 0x2c +#define AR8216_STATS_RXMAXBYTE 0x30 +#define AR8216_STATS_RXTOOLONG 0x34 +#define AR8216_STATS_RXGOODBYTE 0x38 +#define AR8216_STATS_RXBADBYTE 0x40 +#define AR8216_STATS_RXOVERFLOW 0x48 +#define AR8216_STATS_FILTERED 0x4c +#define AR8216_STATS_TXBROAD 0x50 +#define AR8216_STATS_TXPAUSE 0x54 +#define AR8216_STATS_TXMULTI 0x58 +#define AR8216_STATS_TXUNDERRUN 0x5c +#define AR8216_STATS_TX64BYTE 0x60 +#define AR8216_STATS_TX128BYTE 0x64 +#define AR8216_STATS_TX256BYTE 0x68 +#define AR8216_STATS_TX512BYTE 0x6c +#define AR8216_STATS_TX1024BYTE 0x70 +#define AR8216_STATS_TXMAXBYTE 0x74 +#define AR8216_STATS_TXOVERSIZE 0x78 +#define AR8216_STATS_TXBYTE 0x7c +#define AR8216_STATS_TXCOLLISION 0x84 +#define AR8216_STATS_TXABORTCOL 0x88 +#define AR8216_STATS_TXMULTICOL 0x8c +#define AR8216_STATS_TXSINGLECOL 0x90 +#define AR8216_STATS_TXEXCDEFER 0x94 +#define AR8216_STATS_TXDEFER 0x98 +#define AR8216_STATS_TXLATECOL 0x9c + +#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) +#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) +#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 +#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) +#define AR8236_PORT_VLAN_PRIORITY_S 28 + +#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) +#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) +#define AR8236_PORT_VLAN2_MEMBER_S 16 +#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) +#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) +#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 + +#define AR8236_STATS_RXBROAD 0x00 +#define AR8236_STATS_RXPAUSE 0x04 +#define AR8236_STATS_RXMULTI 0x08 +#define AR8236_STATS_RXFCSERR 0x0c +#define AR8236_STATS_RXALIGNERR 0x10 +#define AR8236_STATS_RXRUNT 0x14 +#define AR8236_STATS_RXFRAGMENT 0x18 +#define AR8236_STATS_RX64BYTE 0x1c +#define AR8236_STATS_RX128BYTE 0x20 +#define AR8236_STATS_RX256BYTE 0x24 +#define AR8236_STATS_RX512BYTE 0x28 +#define AR8236_STATS_RX1024BYTE 0x2c +#define AR8236_STATS_RX1518BYTE 0x30 +#define AR8236_STATS_RXMAXBYTE 0x34 +#define AR8236_STATS_RXTOOLONG 0x38 +#define AR8236_STATS_RXGOODBYTE 0x3c +#define AR8236_STATS_RXBADBYTE 0x44 +#define AR8236_STATS_RXOVERFLOW 0x4c +#define AR8236_STATS_FILTERED 0x50 +#define AR8236_STATS_TXBROAD 0x54 +#define AR8236_STATS_TXPAUSE 0x58 +#define AR8236_STATS_TXMULTI 0x5c +#define AR8236_STATS_TXUNDERRUN 0x60 +#define AR8236_STATS_TX64BYTE 0x64 +#define AR8236_STATS_TX128BYTE 0x68 +#define AR8236_STATS_TX256BYTE 0x6c +#define AR8236_STATS_TX512BYTE 0x70 +#define AR8236_STATS_TX1024BYTE 0x74 +#define AR8236_STATS_TX1518BYTE 0x78 +#define AR8236_STATS_TXMAXBYTE 0x7c +#define AR8236_STATS_TXOVERSIZE 0x80 +#define AR8236_STATS_TXBYTE 0x84 +#define AR8236_STATS_TXCOLLISION 0x8c +#define AR8236_STATS_TXABORTCOL 0x90 +#define AR8236_STATS_TXMULTICOL 0x94 +#define AR8236_STATS_TXSINGLECOL 0x98 +#define AR8236_STATS_TXEXCDEFER 0x9c +#define AR8236_STATS_TXDEFER 0xa0 +#define AR8236_STATS_TXLATECOL 0xa4 + +#define AR8316_REG_POSTRIP 0x0008 +#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) +#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) +#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) +#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) +#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) +#define AR8316_POSTRIP_RTL_MODE BIT(5) +#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) +#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) +#define AR8316_POSTRIP_SERDES_EN BIT(8) +#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) +#define AR8316_POSTRIP_GATE_25M_EN BIT(10) +#define AR8316_POSTRIP_SEL_CLK25M BIT(11) +#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) +#define AR8316_POSTRIP_DBG_MODE_I BIT(13) +#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) +#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) +#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) +#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) +#define AR8316_POSTRIP_MAN_EN BIT(18) +#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) +#define AR8316_POSTRIP_LPW_EXIT BIT(20) +#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) +#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) +#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) +#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) +#define AR8316_POSTRIP_SPI_EN BIT(25) +#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) +#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) + +/* port speed */ +enum { + AR8216_PORT_SPEED_10M = 0, + AR8216_PORT_SPEED_100M = 1, + AR8216_PORT_SPEED_1000M = 2, + AR8216_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + AR8216_IN_PORT_ONLY = 0, + AR8216_IN_PORT_FALLBACK = 1, + AR8216_IN_VLAN_ONLY = 2, + AR8216_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + AR8216_OUT_KEEP = 0, + AR8216_OUT_STRIP_VLAN = 1, + AR8216_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + AR8216_PORT_STATE_DISABLED = 0, + AR8216_PORT_STATE_BLOCK = 1, + AR8216_PORT_STATE_LISTEN = 2, + AR8216_PORT_STATE_LEARN = 3, + AR8216_PORT_STATE_FORWARD = 4 +}; + +enum { + AR8XXX_VER_AR8216 = 0x01, + AR8XXX_VER_AR8236 = 0x03, + AR8XXX_VER_AR8316 = 0x10, + AR8XXX_VER_AR8327 = 0x12, + AR8XXX_VER_AR8337 = 0x13, +}; + +#define AR8XXX_NUM_ARL_RECORDS 100 + +enum arl_op { + AR8XXX_ARL_INITIALIZE, + AR8XXX_ARL_GET_NEXT +}; + +struct arl_entry { + u8 port; + u8 mac[6]; +}; + +struct ar8xxx_priv; + +struct ar8xxx_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +struct ar8xxx_chip { + unsigned long caps; + bool config_at_probe; + bool mii_lo_first; + + /* parameters to calculate REG_PORT_STATS_BASE */ + unsigned reg_port_stats_start; + unsigned reg_port_stats_length; + + unsigned reg_arl_ctrl; + + int (*hw_init)(struct ar8xxx_priv *priv); + void (*cleanup)(struct ar8xxx_priv *priv); + + const char *name; + int vlans; + int ports; + const struct switch_dev_ops *swops; + + void (*init_globals)(struct ar8xxx_priv *priv); + void (*init_port)(struct ar8xxx_priv *priv, int port); + void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members); + u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); + u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port); + int (*atu_flush)(struct ar8xxx_priv *priv); + int (*atu_flush_port)(struct ar8xxx_priv *priv, int port); + void (*vtu_flush)(struct ar8xxx_priv *priv); + void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); + void (*phy_fixup)(struct ar8xxx_priv *priv, int phy); + void (*set_mirror_regs)(struct ar8xxx_priv *priv); + void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a, + u32 *status, enum arl_op op); + int (*sw_hw_apply)(struct switch_dev *dev); + + const struct ar8xxx_mib_desc *mib_decs; + unsigned num_mibs; + unsigned mib_func; +}; + +struct ar8xxx_priv { + struct switch_dev dev; + struct mii_bus *mii_bus; + struct phy_device *phy; + + int (*get_port_link)(unsigned port); + + const struct net_device_ops *ndo_old; + struct net_device_ops ndo; + struct mutex reg_mutex; + u8 chip_ver; + u8 chip_rev; + const struct ar8xxx_chip *chip; + void *chip_data; + bool initialized; + bool port4_phy; + char buf[2048]; + struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS]; + char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256]; + bool link_up[AR8X16_MAX_PORTS]; + + bool init; + + struct mutex mib_lock; + struct delayed_work mib_work; + int mib_next_port; + u64 *mib_stats; + + struct list_head list; + unsigned int use_count; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[AR8X16_MAX_VLANS]; + u8 vlan_table[AR8X16_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR8X16_MAX_PORTS]; + int arl_age_time; + + /* mirroring */ + bool mirror_rx; + bool mirror_tx; + int source_port; + int monitor_port; + u8 port_vlan_prio[AR8X16_MAX_PORTS]; +}; + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum); +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val); +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg); +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val); +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data); +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data); +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg); +void +ar8xxx_phy_init(struct ar8xxx_priv *priv); +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan); +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan); +int +ar8xxx_sw_hw_apply(struct switch_dev *dev); +int +ar8xxx_sw_reset_switch(struct switch_dev *dev); +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link); +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +static inline struct ar8xxx_priv * +swdev_to_ar8xxx(struct switch_dev *swdev) +{ + return container_of(swdev, struct ar8xxx_priv, dev); +} + +static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_GIGE; +} + +static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; +} + +static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8216; +} + +static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8236; +} + +static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8316; +} + +static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8327; +} + +static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8337; +} + +static inline void +ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, 0, val); +} + +static inline void +ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, val, 0); +} + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +static inline void +wait_for_page_switch(void) +{ + udelay(5); +} + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/ar8327.c b/target/linux/generic/files-3.18/drivers/net/phy/ar8327.c new file mode 100644 index 0000000000..74f0a08d76 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/ar8327.c @@ -0,0 +1,1503 @@ +/* + * ar8327.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" +#include "ar8327.h" + +extern const struct ar8xxx_mib_desc ar8236_mibs[39]; +extern const struct switch_attr ar8xxx_sw_attr_vlan[1]; + +static u32 +ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) +{ + u32 t; + + if (!cfg) + return 0; + + t = 0; + switch (cfg->mode) { + case AR8327_PAD_NC: + break; + + case AR8327_PAD_MAC2MAC_MII: + t = AR8327_PAD_MAC_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2MAC_GMII: + t = AR8327_PAD_MAC_GMII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_SGMII: + t = AR8327_PAD_SGMII_EN; + + /* + * WAR for the QUalcomm Atheros AP136 board. + * It seems that RGMII TX/RX delay settings needs to be + * applied for SGMII mode as well, The ethernet is not + * reliable without this. + */ + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + + if (cfg->sgmii_delay_en) + t |= AR8327_PAD_SGMII_DELAY_EN; + + break; + + case AR8327_PAD_MAC2PHY_MII: + t = AR8327_PAD_PHY_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2PHY_GMII: + t = AR8327_PAD_PHY_GMII_EN; + if (cfg->pipe_rxclk_sel) + t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_RGMII: + t = AR8327_PAD_RGMII_EN; + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + break; + + case AR8327_PAD_PHY_GMII: + t = AR8327_PAD_PHYX_GMII_EN; + break; + + case AR8327_PAD_PHY_RGMII: + t = AR8327_PAD_PHYX_RGMII_EN; + break; + + case AR8327_PAD_PHY_MII: + t = AR8327_PAD_PHYX_MII_EN; + break; + } + + return t; +} + +static void +ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) +{ + switch (priv->chip_rev) { + case 1: + /* For 100M waveform */ + ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); + /* Turn on Gigabit clock */ + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); + break; + + case 2: + ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); + /* fallthrough */ + case 4: + ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); + ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); + ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); + break; + } +} + +static u32 +ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) +{ + u32 t; + + if (!cfg->force_link) + return AR8216_PORT_STATUS_LINK_AUTO; + + t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; + t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; + t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; + t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; + + switch (cfg->speed) { + case AR8327_PORT_SPEED_10: + t |= AR8216_PORT_SPEED_10M; + break; + case AR8327_PORT_SPEED_100: + t |= AR8216_PORT_SPEED_100M; + break; + case AR8327_PORT_SPEED_1000: + t |= AR8216_PORT_SPEED_1000M; + break; + } + + return t; +} + +#define AR8327_LED_ENTRY(_num, _reg, _shift) \ + [_num] = { .reg = (_reg), .shift = (_shift) } + +static const struct ar8327_led_entry +ar8327_led_map[AR8327_NUM_LEDS] = { + AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), + + AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), + AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), + AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), + + AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), + AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), + + AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), + AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), + AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), + + AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), +}; + +static void +ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, + enum ar8327_led_pattern pattern) +{ + const struct ar8327_led_entry *entry; + + entry = &ar8327_led_map[led_num]; + ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), + (3 << entry->shift), pattern << entry->shift); +} + +static void +ar8327_led_work_func(struct work_struct *work) +{ + struct ar8327_led *aled; + u8 pattern; + + aled = container_of(work, struct ar8327_led, led_work); + + pattern = aled->pattern; + + ar8327_set_led_pattern(aled->sw_priv, aled->led_num, + pattern); +} + +static void +ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) +{ + if (aled->pattern == pattern) + return; + + aled->pattern = pattern; + schedule_work(&aled->led_work); +} + +static inline struct ar8327_led * +led_cdev_to_ar8327_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct ar8327_led, cdev); +} + +static int +ar8327_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + + if (*delay_on == 0 && *delay_off == 0) { + *delay_on = 125; + *delay_off = 125; + } + + if (*delay_on != 125 || *delay_off != 125) { + /* + * The hardware only supports blinking at 4Hz. Fall back + * to software implementation in other cases. + */ + return -EINVAL; + } + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); + + spin_unlock(&aled->lock); + + return 0; +} + +static void +ar8327_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + bool active; + + active = (brightness != LED_OFF); + active ^= aled->active_low; + + pattern = (active) ? AR8327_LED_PATTERN_ON : + AR8327_LED_PATTERN_OFF; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); +} + +static ssize_t +ar8327_led_enable_hw_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + ssize_t ret = 0; + + ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); + + return ret; +} + +static ssize_t +ar8327_led_enable_hw_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + u8 value; + int ret; + + ret = kstrtou8(buf, 10, &value); + if (ret < 0) + return -EINVAL; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = !!value; + if (aled->enable_hw_mode) + pattern = AR8327_LED_PATTERN_RULE; + else + pattern = AR8327_LED_PATTERN_OFF; + + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); + + return size; +} + +static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, + ar8327_led_enable_hw_mode_show, + ar8327_led_enable_hw_mode_store); + +static int +ar8327_led_register(struct ar8327_led *aled) +{ + int ret; + + ret = led_classdev_register(NULL, &aled->cdev); + if (ret < 0) + return ret; + + if (aled->mode == AR8327_LED_MODE_HW) { + ret = device_create_file(aled->cdev.dev, + &dev_attr_enable_hw_mode); + if (ret) + goto err_unregister; + } + + return 0; + +err_unregister: + led_classdev_unregister(&aled->cdev); + return ret; +} + +static void +ar8327_led_unregister(struct ar8327_led *aled) +{ + if (aled->mode == AR8327_LED_MODE_HW) + device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); + + led_classdev_unregister(&aled->cdev); + cancel_work_sync(&aled->led_work); +} + +static int +ar8327_led_create(struct ar8xxx_priv *priv, + const struct ar8327_led_info *led_info) +{ + struct ar8327_data *data = priv->chip_data; + struct ar8327_led *aled; + int ret; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return 0; + + if (!led_info->name) + return -EINVAL; + + if (led_info->led_num >= AR8327_NUM_LEDS) + return -EINVAL; + + aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, + GFP_KERNEL); + if (!aled) + return -ENOMEM; + + aled->sw_priv = priv; + aled->led_num = led_info->led_num; + aled->active_low = led_info->active_low; + aled->mode = led_info->mode; + + if (aled->mode == AR8327_LED_MODE_HW) + aled->enable_hw_mode = true; + + aled->name = (char *)(aled + 1); + strcpy(aled->name, led_info->name); + + aled->cdev.name = aled->name; + aled->cdev.brightness_set = ar8327_led_set_brightness; + aled->cdev.blink_set = ar8327_led_blink_set; + aled->cdev.default_trigger = led_info->default_trigger; + + spin_lock_init(&aled->lock); + mutex_init(&aled->mutex); + INIT_WORK(&aled->led_work, ar8327_led_work_func); + + ret = ar8327_led_register(aled); + if (ret) + goto err_free; + + data->leds[data->num_leds++] = aled; + + return 0; + +err_free: + kfree(aled); + return ret; +} + +static void +ar8327_led_destroy(struct ar8327_led *aled) +{ + ar8327_led_unregister(aled); + kfree(aled); +} + +static void +ar8327_leds_init(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + + if (aled->enable_hw_mode) + aled->pattern = AR8327_LED_PATTERN_RULE; + else + aled->pattern = AR8327_LED_PATTERN_OFF; + + ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); + } +} + +static void +ar8327_leds_cleanup(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + ar8327_led_destroy(aled); + } + + kfree(data->leds); +} + +static int +ar8327_hw_config_pdata(struct ar8xxx_priv *priv, + struct ar8327_platform_data *pdata) +{ + struct ar8327_led_cfg *led_cfg; + struct ar8327_data *data = priv->chip_data; + u32 pos, new_pos; + u32 t; + + if (!pdata) + return -EINVAL; + + priv->get_port_link = pdata->get_port_link; + + data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); + data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); + + t = ar8327_get_pad_cfg(pdata->pad0_cfg); + if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) + t |= AR8337_PAD_MAC06_EXCHANGE_EN; + ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); + + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad6_cfg); + ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); + + pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRIP); + new_pos = pos; + + led_cfg = pdata->led_cfg; + if (led_cfg) { + if (led_cfg->open_drain) + new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; + else + new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; + + ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); + ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); + ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); + ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); + + if (new_pos != pos) + new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; + } + + if (pdata->sgmii_cfg) { + t = pdata->sgmii_cfg->sgmii_ctrl; + if (priv->chip_rev == 1) + t |= AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX; + else + t &= ~(AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX); + + ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t); + + if (pdata->sgmii_cfg->serdes_aen) + new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; + else + new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; + } + + ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); + + if (pdata->leds && pdata->num_leds) { + int i; + + data->leds = kzalloc(pdata->num_leds * sizeof(void *), + GFP_KERNEL); + if (!data->leds) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) + ar8327_led_create(priv, &pdata->leds[i]); + } + + return 0; +} + +#ifdef CONFIG_OF +static int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + struct ar8327_data *data = priv->chip_data; + const __be32 *paddr; + int len; + int i; + + paddr = of_get_property(np, "qca,ar8327-initvals", &len); + if (!paddr || len < (2 * sizeof(*paddr))) + return -EINVAL; + + len /= sizeof(*paddr); + + for (i = 0; i < len - 1; i += 2) { + u32 reg; + u32 val; + + reg = be32_to_cpup(paddr + i); + val = be32_to_cpup(paddr + i + 1); + + switch (reg) { + case AR8327_REG_PORT_STATUS(0): + data->port0_status = val; + break; + case AR8327_REG_PORT_STATUS(6): + data->port6_status = val; + break; + default: + ar8xxx_write(priv, reg, val); + break; + } + } + + return 0; +} +#else +static inline int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + return -EINVAL; +} +#endif + +static int +ar8327_hw_init(struct ar8xxx_priv *priv) +{ + int ret; + + priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL); + if (!priv->chip_data) + return -ENOMEM; + + if (priv->phy->mdio.dev.of_node) + ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node); + else + ret = ar8327_hw_config_pdata(priv, + priv->phy->mdio.dev.platform_data); + + if (ret) + return ret; + + ar8327_leds_init(priv); + + ar8xxx_phy_init(priv); + + return 0; +} + +static void +ar8327_cleanup(struct ar8xxx_priv *priv) +{ + ar8327_leds_cleanup(priv); +} + +static void +ar8327_init_globals(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + int i; + + /* enable CPU port and disable mirror port */ + t = AR8327_FWD_CTRL0_CPU_PORT_EN | + AR8327_FWD_CTRL0_MIRROR_PORT; + ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t); + + /* forward multicast and broadcast frames to CPU */ + t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); + ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, + AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, + AR8327_MODULE_EN_MIB); + + /* Disable EEE on all phy's due to stability issues */ + for (i = 0; i < AR8XXX_NUM_PHYS; i++) + data->eee[i] = false; +} + +static void +ar8327_init_port(struct ar8xxx_priv *priv, int port) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + + if (port == AR8216_PORT_CPU) + t = data->port0_status; + else if (port == 6) + t = data->port6_status; + else + t = AR8216_PORT_STATUS_LINK_AUTO; + + if (port != AR8216_PORT_CPU && port != 6) { + /*hw limitation:if configure mac when there is traffic, + port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); + msleep(100); + t |= AR8216_PORT_STATUS_FLOW_CONTROL; + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } else { + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } + + ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); + + t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = AR8327_PORT_LOOKUP_LEARN; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static u32 +ar8327_read_port_status(struct ar8xxx_priv *priv, int port) +{ + u32 t; + + t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); + /* map the flow control autoneg result bits to the flow control bits + * used in forced mode to allow ar8216_read_port_link detect + * flow control properly if autoneg is used + */ + if (t & AR8216_PORT_STATUS_LINK_UP && + t & AR8216_PORT_STATUS_LINK_AUTO) { + t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); + if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) + t |= AR8216_PORT_STATUS_TXFLOW; + if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) + t |= AR8216_PORT_STATUS_RXFLOW; + } + + return t; +} + +static u32 +ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) +{ + int phy; + u16 t; + + if (port >= priv->dev.ports) + return 0; + + if (port == 0 || port == 6) + return 0; + + phy = port - 1; + + /* EEE Ability Auto-negotiation Result */ + t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); + + return mmd_eee_adv_to_ethtool_adv_t(t); +} + +static int +ar8327_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_OP_FLUSH | + AR8327_ATU_FUNC_BUSY); + + return ret; +} + +static int +ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) { + t = (port << AR8327_ATU_PORT_NUM_S); + t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; + t |= AR8327_ATU_FUNC_BUSY; + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); + } + + return ret; +} + +static int +ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) +{ + u32 fwd_ctrl, frame_ack; + + fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port)); + + return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & + fwd_ctrl) == fwd_ctrl && + (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & + frame_ack) == frame_ack; +} + +static void +ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) +{ + int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); + u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port); + + if (enable) { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); + } else { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); + ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); + } +} + +static void +ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, + AR8327_VTU_FUNC1_BUSY, 0)) + return; + + if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) + ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val); + + op |= AR8327_VTU_FUNC1_BUSY; + ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op); +} + +static void +ar8327_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); +} + +static void +ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + u32 val; + int i; + + op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); + val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; + for (i = 0; i < AR8327_NUM_PORTS; i++) { + u32 mode; + + if ((port_mask & BIT(i)) == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_NOT; + else if (priv->vlan == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; + else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid)) + mode = AR8327_VTU_FUNC0_EG_MODE_TAG; + else + mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; + + val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); + } + ar8327_vtu_op(priv, op, val); +} + +static void +ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 t; + u32 egress, ingress; + u32 pvid = priv->vlan_id[priv->pvid[port]]; + + if (priv->vlan) { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; + ingress = AR8216_IN_SECURE; + } else { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; + ingress = AR8216_IN_PORT_ONLY; + } + + t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; + t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; + if (priv->vlan && priv->port_vlan_prio[port]) { + u32 prio = priv->port_vlan_prio[port]; + + t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S; + t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S; + } + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); + + t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; + t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S; + if (priv->vlan && priv->port_vlan_prio[port]) + t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP; + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = members; + t |= AR8327_PORT_LOOKUP_LEARN; + t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static int +ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (val->port_vlan == priv->pvid[p->id]) { + priv->vlan_tagged |= (1 << p->id); + } + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8327_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + for (port = 0; port < AR8327_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8327_NUM_PORTS || + priv->monitor_port >= AR8327_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); +} + +static int +ar8327_sw_set_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + data->eee[phy] = !!(val->value.i); + + return 0; +} + +static int +ar8327_sw_get_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + val->value.i = data->eee[phy]; + + return 0; +} + +static void +ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8327: timeout waiting for atu to become ready\n"); +} + +static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_data0, r1_data1, r1_data2, r1_func; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); + r2 |= 0x10; + + r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; + r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; + r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8327_wait_atu_ready(priv, r2, r1_func); + + ar8xxx_mii_write32(priv, r2, r1_data0, 0); + ar8xxx_mii_write32(priv, r2, r1_data1, 0); + ar8xxx_mii_write32(priv, r2, r1_data2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + ar8xxx_mii_write32(priv, r2, r1_func, + AR8327_ATU_FUNC_OP_GET_NEXT | + AR8327_ATU_FUNC_BUSY); + ar8327_wait_atu_ready(priv, r2, r1_func); + + val0 = ar8xxx_mii_read32(priv, r2, r1_data0); + val1 = ar8xxx_mii_read32(priv, r2, r1_data1); + val2 = ar8xxx_mii_read32(priv, r2, r1_data2); + + *status = val2 & AR8327_ATU_STATUS; + if (!*status) + break; + + i = 0; + t = AR8327_ATU_PORT0; + while (!(val1 & t) && ++i < AR8327_NUM_PORTS) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; + a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; + a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; + a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; + a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; + a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; + break; + } +} + +static int +ar8327_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int ret, i; + + ret = ar8xxx_sw_hw_apply(dev); + if (ret) + return ret; + + for (i=0; i < AR8XXX_NUM_PHYS; i++) { + if (data->eee[i]) + ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + else + ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + } + + return 0; +} + +int +ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + val->value.i = ar8327_get_port_igmp(priv, port); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ar8327_set_port_igmp(priv, port, val->value.i); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_get_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || + !val->value.i) + break; + } + + return 0; +} + +int +ar8327_sw_set_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) + break; + } + + return 0; +} + +int +ar8327_sw_get_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u32 val_reg; + + mutex_lock(&priv->reg_mutex); + val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); + val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + if (val->value.i) + ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + else + ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static int +ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + if (val->value.i < 0 || val->value.i > 7) + return -EINVAL; + + priv->port_vlan_prio[port] = val->value.i; + + return 0; +} + +static int +ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + val->value.i = priv->port_vlan_prio[port]; + + return 0; +} + +static const struct switch_attr ar8327_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "arl_age_time", + .description = "ARL age time (secs)", + .set = ar8xxx_sw_set_arl_age_time, + .get = ar8xxx_sw_get_arl_age_time, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable IGMP Snooping", + .set = ar8327_sw_set_igmp_snooping, + .get = ar8327_sw_get_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_v3", + .description = "Enable IGMPv3 support", + .set = ar8327_sw_set_igmp_v3, + .get = ar8327_sw_get_igmp_v3, + .max = 1 + }, +}; + +static const struct switch_attr ar8327_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_eee", + .description = "Enable EEE PHY sleep mode", + .set = ar8327_sw_set_eee, + .get = ar8327_sw_get_eee, + .max = 1, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable port's IGMP Snooping", + .set = ar8327_sw_set_port_igmp_snooping, + .get = ar8327_sw_get_port_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "vlan_prio", + .description = "Port VLAN default priority (VLAN PCP) (0-7)", + .set = ar8327_sw_set_port_vlan_prio, + .get = ar8327_sw_get_port_vlan_prio, + .max = 7, + }, +}; + +static const struct switch_dev_ops ar8327_sw_ops = { + .attr_global = { + .attr = ar8327_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), + }, + .attr_port = { + .attr = ar8327_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8327_sw_get_ports, + .set_vlan_ports = ar8327_sw_set_ports, + .apply_config = ar8327_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +const struct ar8xxx_chip ar8327_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8327", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; + +const struct ar8xxx_chip ar8337_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8337", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; diff --git a/target/linux/generic/files-3.18/drivers/net/phy/ar8327.h b/target/linux/generic/files-3.18/drivers/net/phy/ar8327.h new file mode 100644 index 0000000000..d53ef885b1 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/ar8327.h @@ -0,0 +1,325 @@ +/* + * ar8327.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8327_H +#define __AR8327_H + +#define AR8327_NUM_PORTS 7 +#define AR8327_NUM_LEDS 15 +#define AR8327_PORTS_ALL 0x7f +#define AR8327_NUM_LED_CTRL_REGS 4 + +#define AR8327_REG_MASK 0x000 + +#define AR8327_REG_PAD0_MODE 0x004 +#define AR8327_REG_PAD5_MODE 0x008 +#define AR8327_REG_PAD6_MODE 0x00c +#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define AR8327_PAD_MAC_MII_EN BIT(2) +#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define AR8327_PAD_MAC_GMII_EN BIT(6) +#define AR8327_PAD_SGMII_EN BIT(7) +#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define AR8327_PAD_PHY_MII_EN BIT(10) +#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define AR8327_PAD_PHY_GMII_EN BIT(14) +#define AR8327_PAD_PHYX_GMII_EN BIT(16) +#define AR8327_PAD_PHYX_RGMII_EN BIT(17) +#define AR8327_PAD_PHYX_MII_EN BIT(18) +#define AR8327_PAD_SGMII_DELAY_EN BIT(19) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define AR8327_PAD_RGMII_EN BIT(26) + +#define AR8327_REG_POWER_ON_STRIP 0x010 +#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) +#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) +#define AR8327_POWER_ON_STRIP_SERDES_AEN BIT(7) + +#define AR8327_REG_INT_STATUS0 0x020 +#define AR8327_INT0_VT_DONE BIT(20) + +#define AR8327_REG_INT_STATUS1 0x024 +#define AR8327_REG_INT_MASK0 0x028 +#define AR8327_REG_INT_MASK1 0x02c + +#define AR8327_REG_MODULE_EN 0x030 +#define AR8327_MODULE_EN_MIB BIT(0) + +#define AR8327_REG_MIB_FUNC 0x034 +#define AR8327_MIB_CPU_KEEP BIT(20) + +#define AR8327_REG_SERVICE_TAG 0x048 +#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4) +#define AR8327_REG_LED_CTRL0 0x050 +#define AR8327_REG_LED_CTRL1 0x054 +#define AR8327_REG_LED_CTRL2 0x058 +#define AR8327_REG_LED_CTRL3 0x05c +#define AR8327_REG_MAC_ADDR0 0x060 +#define AR8327_REG_MAC_ADDR1 0x064 + +#define AR8327_REG_MAX_FRAME_SIZE 0x078 +#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) + +#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) +#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10) +#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11) + +#define AR8327_REG_HEADER_CTRL 0x098 +#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) + +#define AR8327_REG_SGMII_CTRL 0x0e0 +#define AR8327_SGMII_CTRL_EN_PLL BIT(1) +#define AR8327_SGMII_CTRL_EN_RX BIT(2) +#define AR8327_SGMII_CTRL_EN_TX BIT(3) + +#define AR8327_REG_EEE_CTRL 0x100 +#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2) + +#define AR8327_REG_FRAME_ACK_CTRL0 0x210 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30) + +#define AR8327_REG_FRAME_ACK_CTRL1 0x214 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24) +#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25) + +#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6) +#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8) + +#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3) +#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define AR8327_PORT_VLAN0_DEF_SVID_S 0 +#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3) +#define AR8327_PORT_VLAN0_DEF_SPRI_S 13 +#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define AR8327_PORT_VLAN0_DEF_CVID_S 16 +#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3) +#define AR8327_PORT_VLAN0_DEF_CPRI_S 29 + +#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4) +#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define AR8327_PORT_VLAN1_OUT_MODE_S 12 +#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +#define AR8327_REG_ATU_DATA0 0x600 +#define AR8327_ATU_ADDR0 BITS(0, 8) +#define AR8327_ATU_ADDR0_S 0 +#define AR8327_ATU_ADDR1 BITS(8, 8) +#define AR8327_ATU_ADDR1_S 8 +#define AR8327_ATU_ADDR2 BITS(16, 8) +#define AR8327_ATU_ADDR2_S 16 +#define AR8327_ATU_ADDR3 BITS(24, 8) +#define AR8327_ATU_ADDR3_S 24 +#define AR8327_REG_ATU_DATA1 0x604 +#define AR8327_ATU_ADDR4 BITS(0, 8) +#define AR8327_ATU_ADDR4_S 0 +#define AR8327_ATU_ADDR5 BITS(8, 8) +#define AR8327_ATU_ADDR5_S 8 +#define AR8327_ATU_PORTS BITS(16, 7) +#define AR8327_ATU_PORT0 BIT(16) +#define AR8327_ATU_PORT1 BIT(17) +#define AR8327_ATU_PORT2 BIT(18) +#define AR8327_ATU_PORT3 BIT(19) +#define AR8327_ATU_PORT4 BIT(20) +#define AR8327_ATU_PORT5 BIT(21) +#define AR8327_ATU_PORT6 BIT(22) +#define AR8327_REG_ATU_DATA2 0x608 +#define AR8327_ATU_STATUS BITS(0, 4) + +#define AR8327_REG_ATU_FUNC 0x60c +#define AR8327_ATU_FUNC_OP BITS(0, 4) +#define AR8327_ATU_FUNC_OP_NOOP 0x0 +#define AR8327_ATU_FUNC_OP_FLUSH 0x1 +#define AR8327_ATU_FUNC_OP_LOAD 0x2 +#define AR8327_ATU_FUNC_OP_PURGE 0x3 +#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4 +#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5 +#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 +#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 +#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 +#define AR8327_ATU_PORT_NUM BITS(8, 4) +#define AR8327_ATU_PORT_NUM_S 8 +#define AR8327_ATU_FUNC_BUSY BIT(31) + +#define AR8327_REG_VTU_FUNC0 0x0610 +#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) +#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 +#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 +#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 +#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 +#define AR8327_VTU_FUNC0_IVL BIT(19) +#define AR8327_VTU_FUNC0_VALID BIT(20) + +#define AR8327_REG_VTU_FUNC1 0x0614 +#define AR8327_VTU_FUNC1_OP BITS(0, 3) +#define AR8327_VTU_FUNC1_OP_NOOP 0 +#define AR8327_VTU_FUNC1_OP_FLUSH 1 +#define AR8327_VTU_FUNC1_OP_LOAD 2 +#define AR8327_VTU_FUNC1_OP_PURGE 3 +#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 +#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 +#define AR8327_VTU_FUNC1_OP_GET_ONE 6 +#define AR8327_VTU_FUNC1_FULL BIT(4) +#define AR8327_VTU_FUNC1_PORT BIT(8, 4) +#define AR8327_VTU_FUNC1_PORT_S 8 +#define AR8327_VTU_FUNC1_VID BIT(16, 12) +#define AR8327_VTU_FUNC1_VID_S 16 +#define AR8327_VTU_FUNC1_BUSY BIT(31) + +#define AR8327_REG_ARL_CTRL 0x0618 + +#define AR8327_REG_FWD_CTRL0 0x620 +#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) +#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) +#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 + +#define AR8327_REG_FWD_CTRL1 0x624 +#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) +#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 +#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) +#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 +#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) +#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 +#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) +#define AR8327_FWD_CTRL1_IGMP_S 24 + +#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) +#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define AR8327_PORT_LOOKUP_IN_MODE_S 8 +#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) +#define AR8327_PORT_LOOKUP_STATE_S 16 +#define AR8327_PORT_LOOKUP_LEARN BIT(20) +#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25) + +#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) + +#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) +#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) + +#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31) + +enum ar8327_led_pattern { + AR8327_LED_PATTERN_OFF = 0, + AR8327_LED_PATTERN_BLINK, + AR8327_LED_PATTERN_ON, + AR8327_LED_PATTERN_RULE, +}; + +struct ar8327_led_entry { + unsigned reg; + unsigned shift; +}; + +struct ar8327_led { + struct led_classdev cdev; + struct ar8xxx_priv *sw_priv; + + char *name; + bool active_low; + u8 led_num; + enum ar8327_led_mode mode; + + struct mutex mutex; + spinlock_t lock; + struct work_struct led_work; + bool enable_hw_mode; + enum ar8327_led_pattern pattern; +}; + +struct ar8327_data { + u32 port0_status; + u32 port6_status; + + struct ar8327_led **leds; + unsigned int num_leds; + + /* all fields below are cleared on reset */ + bool eee[AR8XXX_NUM_PHYS]; +}; + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/Kconfig b/target/linux/generic/files-3.18/drivers/net/phy/b53/Kconfig new file mode 100644 index 0000000000..08287e7adf --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/Kconfig @@ -0,0 +1,37 @@ +menuconfig SWCONFIG_B53 + tristate "Broadcom bcm53xx managed switch support" + depends on SWCONFIG + help + This driver adds support for Broadcom managed switch chips. It supports + BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX + integrated switches. + +config SWCONFIG_B53_SPI_DRIVER + tristate "B53 SPI connected switch driver" + depends on SWCONFIG_B53 && SPI + help + Select to enable support for registering switches configured through SPI. + +config SWCONFIG_B53_PHY_DRIVER + tristate "B53 MDIO connected switch driver" + depends on SWCONFIG_B53 + select SWCONFIG_B53_PHY_FIXUP + help + Select to enable support for registering switches configured through MDIO. + +config SWCONFIG_B53_MMAP_DRIVER + tristate "B53 MMAP connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped switches like the BCM63XX + integrated switches. + +config SWCONFIG_B53_SRAB_DRIVER + tristate "B53 SRAB connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped Switch Register Access + Bridge Registers (SRAB) like it is found on the BCM53010 + +config SWCONFIG_B53_PHY_FIXUP + bool diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/Makefile b/target/linux/generic/files-3.18/drivers/net/phy/b53/Makefile new file mode 100644 index 0000000000..13ff366448 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_SWCONFIG_B53) += b53_common.o + +obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o + +obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o +obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o +obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o +obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o + +ccflags-y += -Werror diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_common.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_common.c new file mode 100644 index 0000000000..670588c84e --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_common.c @@ -0,0 +1,1722 @@ +/* + * B53 switch driver main logic + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b53_regs.h" +#include "b53_priv.h" + +/* buffer size needed for displaying all MIBs with max'd values */ +#define B53_BUF_SIZE 1188 + +struct b53_mib_desc { + u8 size; + u8 offset; + const char *name; +}; + +/* BCM5365 MIB counters */ +static const struct b53_mib_desc b53_mibs_65[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { }, +}; + +#define B63XX_MIB_TXB_ID 0 /* TxOctets */ +#define B63XX_MIB_RXB_ID 14 /* RxOctets */ + +/* BCM63xx MIB counters */ +static const struct b53_mib_desc b53_mibs_63xx[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x0c, "TxQoSPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x3c, "TxQoSOctets" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { 4, 0xa0, "RxSymbolErrors" }, + { 4, 0xa4, "RxQoSPkts" }, + { 8, 0xa8, "RxQoSOctets" }, + { 4, 0xb0, "Pkts1523to2047Octets" }, + { 4, 0xb4, "Pkts2048to4095Octets" }, + { 4, 0xb8, "Pkts4096to8191Octets" }, + { 4, 0xbc, "Pkts8192to9728Octets" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +#define B53XX_MIB_TXB_ID 0 /* TxOctets */ +#define B53XX_MIB_RXB_ID 12 /* RxOctets */ + +/* MIB counters */ +static const struct b53_mib_desc b53_mibs[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x50, "RxOctets" }, + { 4, 0x58, "RxUndersizePkts" }, + { 4, 0x5c, "RxPausePkts" }, + { 4, 0x60, "Pkts64Octets" }, + { 4, 0x64, "Pkts65to127Octets" }, + { 4, 0x68, "Pkts128to255Octets" }, + { 4, 0x6c, "Pkts256to511Octets" }, + { 4, 0x70, "Pkts512to1023Octets" }, + { 4, 0x74, "Pkts1024to1522Octets" }, + { 4, 0x78, "RxOversizePkts" }, + { 4, 0x7c, "RxJabbers" }, + { 4, 0x80, "RxAlignmentErrors" }, + { 4, 0x84, "RxFCSErrors" }, + { 8, 0x88, "RxGoodOctets" }, + { 4, 0x90, "RxDropPkts" }, + { 4, 0x94, "RxUnicastPkts" }, + { 4, 0x98, "RxMulticastPkts" }, + { 4, 0x9c, "RxBroadcastPkts" }, + { 4, 0xa0, "RxSAChanges" }, + { 4, 0xa4, "RxFragments" }, + { 4, 0xa8, "RxJumboPkts" }, + { 4, 0xac, "RxSymbolErrors" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +static int b53_do_vlan_op(struct b53_device *dev, u8 op) +{ + unsigned int i; + + b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); + + for (i = 0; i < 10; i++) { + u8 vta; + + b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); + if (!(vta & VTA_START_CMD)) + return 0; + + usleep_range(100, 200); + } + + return -EIO; +} + +static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, + u16 untag) +{ + if (is5325(dev)) { + u32 entry = 0; + + if (members) { + entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | + members; + if (dev->core_rev >= 3) + entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; + else + entry |= VA_VALID_25; + } + + b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else if (is5365(dev)) { + u16 entry = 0; + + if (members) + entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | + members | VA_VALID_65; + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else { + b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); + b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], + (untag << VTE_UNTAG_S) | members); + + b53_do_vlan_op(dev, VTA_CMD_WRITE); + } +} + +void b53_set_forwarding(struct b53_device *dev, int enable) +{ + u8 mgmt; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (enable) + mgmt |= SM_SW_FWD_EN; + else + mgmt &= ~SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static void b53_enable_vlan(struct b53_device *dev, int enable) +{ + u8 mgmt, vc0, vc1, vc4 = 0, vc5; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); + + if (is5325(dev) || is5365(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); + } else if (is63xx(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); + } else { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); + } + + mgmt &= ~SM_SW_FWD_MODE; + + if (enable) { + vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; + vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; + vc5 |= VC5_DROP_VTABLE_MISS; + + if (is5325(dev)) + vc0 &= ~VC0_RESERVED_1; + + if (is5325(dev) || is5365(dev)) + vc1 |= VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) { + if (dev->allow_vid_4095) + vc5 |= VC5_VID_FFF_EN; + else + vc5 &= ~VC5_VID_FFF_EN; + } + } else { + vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); + vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc5 &= ~VC5_DROP_VTABLE_MISS; + + if (is5325(dev) || is5365(dev)) + vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; + else + vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; + + if (is5325(dev) || is5365(dev)) + vc1 &= ~VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) + vc5 &= ~VC5_VID_FFF_EN; + } + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); + + if (is5325(dev) || is5365(dev)) { + /* enable the high 8 bit vid check on 5325 */ + if (is5325(dev) && enable) + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, + VC3_HIGH_8BIT_EN); + else + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); + } else if (is63xx(dev)) { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); + } else { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); + } + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) +{ + u32 port_mask = 0; + u16 max_size = JMS_MIN_SIZE; + + if (is5325(dev) || is5365(dev)) + return -EINVAL; + + if (enable) { + port_mask = dev->enabled_ports; + max_size = JMS_MAX_SIZE; + if (allow_10_100) + port_mask |= JPM_10_100_JUMBO_EN; + } + + b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); + return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); +} + +static int b53_flush_arl(struct b53_device *dev) +{ + unsigned int i; + + b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); + + for (i = 0; i < 10; i++) { + u8 fast_age_ctrl; + + b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + &fast_age_ctrl); + + if (!(fast_age_ctrl & FAST_AGE_DONE)) + return 0; + + mdelay(1); + } + + pr_warn("time out while flushing ARL\n"); + + return -EINVAL; +} + +static void b53_enable_ports(struct b53_device *dev) +{ + unsigned i; + + b53_for_each_port(dev, i) { + u8 port_ctrl; + u16 pvlan_mask; + + /* + * prevent leaking packets between wan and lan in unmanaged + * mode through port vlans. + */ + if (dev->enable_vlan || is_cpu_port(dev, i)) + pvlan_mask = 0x1ff; + else if (is531x5(dev) || is5301x(dev)) + /* BCM53115 may use a different port as cpu port */ + pvlan_mask = BIT(dev->sw_dev.cpu_port); + else + pvlan_mask = BIT(B53_CPU_PORT); + + /* BCM5325 CPU port is at 8 */ + if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) + i = B53_CPU_PORT; + + if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) + /* disable unused ports 6 & 7 */ + port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; + else if (i == B53_CPU_PORT) + port_ctrl = PORT_CTRL_RX_BCST_EN | + PORT_CTRL_RX_MCST_EN | + PORT_CTRL_RX_UCST_EN; + else + port_ctrl = 0; + + b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), + pvlan_mask); + + /* port state is handled by bcm63xx_enet driver */ + if (!is63xx(dev) && !(is5301x(dev) && i == 6)) + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), + port_ctrl); + } +} + +static void b53_enable_mib(struct b53_device *dev) +{ + u8 gc; + + b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); + + b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); +} + +static int b53_apply(struct b53_device *dev) +{ + int i; + + /* clear all vlan entries */ + if (is5325(dev) || is5365(dev)) { + for (i = 1; i < dev->sw_dev.vlans; i++) + b53_set_vlan_entry(dev, i, 0, 0); + } else { + b53_do_vlan_op(dev, VTA_CMD_CLEAR); + } + + b53_enable_vlan(dev, dev->enable_vlan); + + /* fill VLAN table */ + if (dev->enable_vlan) { + for (i = 0; i < dev->sw_dev.vlans; i++) { + struct b53_vlan *vlan = &dev->vlans[i]; + + if (!vlan->members) + continue; + + b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); + } + + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), + dev->ports[i].pvid); + } else { + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), 1); + + } + + b53_enable_ports(dev); + + if (!is5325(dev) && !is5365(dev)) + b53_set_jumbo(dev, dev->enable_jumbo, 1); + + return 0; +} + +static void b53_switch_reset_gpio(struct b53_device *dev) +{ + int gpio = dev->reset_gpio; + + if (gpio < 0) + return; + + /* + * Reset sequence: RESET low(50ms)->high(20ms) + */ + gpio_set_value(gpio, 0); + mdelay(50); + + gpio_set_value(gpio, 1); + mdelay(20); + + dev->current_page = 0xff; +} + +static int b53_configure_ports_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + u32 port_num; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + + for_each_available_child_of_node(dn, pn) { + struct device_node *fixed_link; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + fixed_link = of_get_child_by_name(pn, "fixed-link"); + if (fixed_link) { + u32 spd; + u8 po = GMII_PO_LINK; + int mode = of_get_phy_mode(pn); + + if (!of_property_read_u32(fixed_link, "speed", &spd)) { + switch (spd) { + case 10: + po |= GMII_PO_SPEED_10M; + break; + case 100: + po |= GMII_PO_SPEED_100M; + break; + case 2000: + if (is_imp_port(dev, port_num)) + po |= PORT_OVERRIDE_SPEED_2000M; + else + po |= GMII_PO_SPEED_2000M; + /* fall through */ + case 1000: + po |= GMII_PO_SPEED_1000M; + break; + } + } + + if (of_property_read_bool(fixed_link, "full-duplex")) + po |= PORT_OVERRIDE_FULL_DUPLEX; + if (of_property_read_bool(fixed_link, "pause")) + po |= GMII_PO_RX_FLOW; + if (of_property_read_bool(fixed_link, "asym-pause")) + po |= GMII_PO_TX_FLOW; + + if (is_imp_port(dev, port_num)) { + po |= PORT_OVERRIDE_EN; + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) + po |= PORT_OVERRIDE_RV_MII_25; + + b53_write8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, po); + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) { + b53_read8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, &po); + if (!(po & PORT_OVERRIDE_RV_MII_25)) + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } else { + po |= GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, + B53_GMII_PORT_OVERRIDE_CTRL(port_num), + po); + } + } + } + + return 0; +} + +static int b53_configure_ports(struct b53_device *dev) +{ + u8 cpu_port = dev->sw_dev.cpu_port; + + /* configure MII port if necessary */ + if (is5325(dev)) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + /* reverse mii needs to be enabled */ + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_RV_MII_25); + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } + } else if (is531x5(dev) && cpu_port == B53_CPU_PORT) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_EN | + PORT_OVERRIDE_LINK); + + /* BCM47189 has another interface connected to the port 5 */ + if (dev->enabled_ports & BIT(5)) { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } else if (is5301x(dev)) { + if (cpu_port == 8) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + mii_port_override |= PORT_OVERRIDE_LINK | + PORT_OVERRIDE_RX_FLOW | + PORT_OVERRIDE_TX_FLOW | + PORT_OVERRIDE_SPEED_2000M | + PORT_OVERRIDE_EN; + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override); + + /* TODO: Ports 5 & 7 require some extra handling */ + } else { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN | + GMII_PO_SPEED_2000M; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } + + return 0; +} + +static int b53_switch_reset(struct b53_device *dev) +{ + int ret = 0; + u8 mgmt; + + b53_switch_reset_gpio(dev); + + if (is539x(dev)) { + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); + } + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + mgmt &= ~SM_SW_FWD_MODE; + mgmt |= SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + pr_err("Failed to enable switch!\n"); + return -EINVAL; + } + } + + /* enable all ports */ + b53_enable_ports(dev); + + if (dev->dev->of_node) + ret = b53_configure_ports_of(dev); + else + ret = b53_configure_ports(dev); + + if (ret) + return ret; + + b53_enable_mib(dev); + + return b53_flush_arl(dev); +} + +/* + * Swconfig glue functions + */ + +static int b53_global_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_vlan; + + return 0; +} + +static int b53_global_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_vlan = val->value.i; + + return 0; +} + +static int b53_global_get_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_jumbo; + + return 0; +} + +static int b53_global_set_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_jumbo = val->value.i; + + return 0; +} + +static int b53_global_get_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->allow_vid_4095; + + return 0; +} + +static int b53_global_set_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->allow_vid_4095 = val->value.i; + + return 0; +} + +static int b53_global_get_ports(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", + priv->enabled_ports); + val->value.s = priv->buf; + + return 0; +} + +static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + *val = priv->ports[port].pvid; + + return 0; +} + +static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (val > 15 && is5325(priv)) + return -EINVAL; + if (val == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + priv->ports[port].pvid = val; + + return 0; +} + +static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port = &val->value.ports[0]; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + val->len = 0; + + if (!vlan->members) + return 0; + + for (i = 0; i < dev->ports; i++) { + if (!(vlan->members & BIT(i))) + continue; + + + if (!(vlan->untag & BIT(i))) + port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); + else + port->flags = 0; + + port->id = i; + val->len++; + port++; + } + + return 0; +} + +static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + /* only BCM5325 and BCM5365 supports VID 0 */ + if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) + return -EINVAL; + + /* VLAN 4095 needs special handling */ + if (val->port_vlan == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + port = &val->value.ports[0]; + vlan->members = 0; + vlan->untag = 0; + for (i = 0; i < val->len; i++, port++) { + vlan->members |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { + vlan->untag |= BIT(port->id); + priv->ports[port->id].pvid = val->port_vlan; + }; + } + + /* ignore disabled ports */ + vlan->members &= priv->enabled_ports; + vlan->untag &= priv->enabled_ports; + + return 0; +} + +static int b53_port_get_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (is_cpu_port(priv, port)) { + link->link = 1; + link->duplex = 1; + link->speed = is5325(priv) || is5365(priv) ? + SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; + link->aneg = 0; + } else if (priv->enabled_ports & BIT(port)) { + u32 speed; + u16 lnk, duplex; + + b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); + b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); + + lnk = (lnk >> port) & 1; + duplex = (duplex >> port) & 1; + + if (is5325(priv) || is5365(priv)) { + u16 tmp; + + b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); + speed = SPEED_PORT_FE(tmp, port); + } else { + b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); + speed = SPEED_PORT_GE(speed, port); + } + + link->link = lnk; + if (lnk) { + link->duplex = duplex; + switch (speed) { + case SPEED_STAT_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case SPEED_STAT_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case SPEED_STAT_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + } + + link->aneg = 1; + } else { + link->link = 0; + } + + return 0; + +} + +static int b53_port_set_link(struct switch_dev *sw_dev, int port, + struct switch_port_link *link) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + + /* + * TODO: BCM63XX requires special handling as it can have external phys + * and ports might be GE or only FE + */ + if (is63xx(dev)) + return -ENOTSUPP; + + if (port == sw_dev->cpu_port) + return -EINVAL; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && + (is5325(dev) || is5365(dev))) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex) + return -EINVAL; + + return switch_generic_set_link(sw_dev, port, link); +} + +static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_read16) + return priv->ops->phy_read16(priv, addr, reg, value); + + return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_write16) + return priv->ops->phy_write16(priv, addr, reg, value); + + return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_global_reset_switch(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* reset vlans */ + priv->enable_vlan = 0; + priv->enable_jumbo = 0; + priv->allow_vid_4095 = 0; + + memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans); + memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports); + + return b53_switch_reset(priv); +} + +static int b53_global_apply_config(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* disable switching */ + b53_set_forwarding(priv, 0); + + b53_apply(priv); + + /* enable switching */ + b53_set_forwarding(priv, 1); + + return 0; +} + + +static int b53_global_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + u8 gc; + + b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); + mdelay(1); + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); + mdelay(1); + + return 0; +} + +static int b53_port_get_mib(struct switch_dev *sw_dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int port = val->port_vlan; + int len = 0; + + if (!(BIT(port) & dev->enabled_ports)) + return -1; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + for (; mibs->size > 0; mibs++) { + u64 val; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, + &val32); + val = val32; + } + + len += snprintf(dev->buf + len, B53_BUF_SIZE - len, + "%-20s: %llu\n", mibs->name, val); + } + + val->len = len; + val->value.s = dev->buf; + + return 0; +} + +static int b53_port_get_stats(struct switch_dev *sw_dev, int port, + struct switch_port_stats *stats) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int txb_id, rxb_id; + u64 rxb, txb; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + txb_id = B53XX_MIB_TXB_ID; + rxb_id = B53XX_MIB_RXB_ID; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + txb_id = B63XX_MIB_TXB_ID; + rxb_id = B63XX_MIB_RXB_ID; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb); + b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32); + txb = val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32); + rxb = val32; + } + + stats->tx_bytes = txb; + stats->rx_bytes = rxb; + + return 0; +} + +static struct switch_attr b53_global_ops_25[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, +}; + +static struct switch_attr b53_global_ops_65[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, +}; + +static struct switch_attr b53_global_ops[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available Ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_jumbo", + .description = "Enable Jumbo Frames", + .set = b53_global_set_jumbo_enable, + .get = b53_global_get_jumbo_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "allow_vid_4095", + .description = "Allow VID 4095", + .set = b53_global_set_4095_enable, + .get = b53_global_get_4095_enable, + .max = 1, + }, +}; + +static struct switch_attr b53_port_ops[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .get = b53_port_get_mib, + }, +}; + +static struct switch_attr b53_no_ops[] = { +}; + +static const struct switch_dev_ops b53_switch_ops_25 = { + .attr_global = { + .attr = b53_global_ops_25, + .n_attr = ARRAY_SIZE(b53_global_ops_25), + }, + .attr_port = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops_65 = { + .attr_global = { + .attr = b53_global_ops_65, + .n_attr = ARRAY_SIZE(b53_global_ops_65), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops = { + .attr_global = { + .attr = b53_global_ops, + .n_attr = ARRAY_SIZE(b53_global_ops), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +struct b53_chip_data { + u32 chip_id; + const char *dev_name; + const char *alias; + u16 vlans; + u16 enabled_ports; + u8 cpu_port; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + const struct switch_dev_ops *sw_ops; +}; + +#define B53_VTA_REGS \ + { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } +#define B53_VTA_REGS_9798 \ + { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } +#define B53_VTA_REGS_63XX \ + { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } + +static const struct b53_chip_data b53_switch_chips[] = { + { + .chip_id = BCM5325_DEVICE_ID, + .dev_name = "BCM5325", + .alias = "bcm5325", + .vlans = 16, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_25, + }, + { + .chip_id = BCM5365_DEVICE_ID, + .dev_name = "BCM5365", + .alias = "bcm5365", + .vlans = 256, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_65, + }, + { + .chip_id = BCM5395_DEVICE_ID, + .dev_name = "BCM5395", + .alias = "bcm5395", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5397_DEVICE_ID, + .dev_name = "BCM5397", + .alias = "bcm5397", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5398_DEVICE_ID, + .dev_name = "BCM5398", + .alias = "bcm5398", + .vlans = 4096, + .enabled_ports = 0x7f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53115_DEVICE_ID, + .dev_name = "BCM53115", + .alias = "bcm53115", + .vlans = 4096, + .enabled_ports = 0x1f, + .vta_regs = B53_VTA_REGS, + .cpu_port = B53_CPU_PORT, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53125_DEVICE_ID, + .dev_name = "BCM53125", + .alias = "bcm53125", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53128_DEVICE_ID, + .dev_name = "BCM53128", + .alias = "bcm53128", + .vlans = 4096, + .enabled_ports = 0x1ff, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM63XX_DEVICE_ID, + .dev_name = "BCM63xx", + .alias = "bcm63xx", + .vlans = 4096, + .enabled_ports = 0, /* pdata must provide them */ + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_63XX, + .duplex_reg = B53_DUPLEX_STAT_63XX, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53010_DEVICE_ID, + .dev_name = "BCM53010", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53011_DEVICE_ID, + .dev_name = "BCM53011", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53012_DEVICE_ID, + .dev_name = "BCM53012", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53018_DEVICE_ID, + .dev_name = "BCM53018", + .alias = "bcm53018", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53019_DEVICE_ID, + .dev_name = "BCM53019", + .alias = "bcm53019", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, +}; + +static int b53_switch_init_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + const char *alias; + u32 port_num; + u16 ports = 0; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + if (!dn) + return -EINVAL; + + for_each_available_child_of_node(dn, pn) { + const char *label; + int len; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + ports |= BIT(port_num); + + label = of_get_property(pn, "label", &len); + if (label && !strcmp(label, "cpu")) + dev->sw_dev.cpu_port = port_num; + } + + dev->enabled_ports = ports; + + if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias", + &alias)) + dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL); + + return 0; +} + +static int b53_switch_init(struct b53_device *dev) +{ + struct switch_dev *sw_dev = &dev->sw_dev; + unsigned i; + int ret; + + for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { + const struct b53_chip_data *chip = &b53_switch_chips[i]; + + if (chip->chip_id == dev->chip_id) { + sw_dev->name = chip->dev_name; + if (!sw_dev->alias) + sw_dev->alias = chip->alias; + if (!dev->enabled_ports) + dev->enabled_ports = chip->enabled_ports; + dev->duplex_reg = chip->duplex_reg; + dev->vta_regs[0] = chip->vta_regs[0]; + dev->vta_regs[1] = chip->vta_regs[1]; + dev->vta_regs[2] = chip->vta_regs[2]; + dev->jumbo_pm_reg = chip->jumbo_pm_reg; + sw_dev->ops = chip->sw_ops; + sw_dev->cpu_port = chip->cpu_port; + sw_dev->vlans = chip->vlans; + break; + } + } + + if (!sw_dev->name) + return -EINVAL; + + /* check which BCM5325x version we have */ + if (is5325(dev)) { + u8 vc4; + + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + + /* check reserved bits */ + switch (vc4 & 3) { + case 1: + /* BCM5325E */ + break; + case 3: + /* BCM5325F - do not use port 4 */ + dev->enabled_ports &= ~BIT(4); + break; + default: +/* On the BCM47XX SoCs this is the supported internal switch.*/ +#ifndef CONFIG_BCM47XX + /* BCM5325M */ + return -EINVAL; +#else + break; +#endif + } + } else if (dev->chip_id == BCM53115_DEVICE_ID) { + u64 strap_value; + + b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); + /* use second IMP port if GMII is enabled */ + if (strap_value & SV_GMII_CTRL_115) + sw_dev->cpu_port = 5; + } + + if (dev_of_node(dev->dev)) { + ret = b53_switch_init_of(dev); + if (ret) + return ret; + } + + dev->enabled_ports |= BIT(sw_dev->cpu_port); + sw_dev->ports = fls(dev->enabled_ports); + + dev->ports = devm_kzalloc(dev->dev, + sizeof(struct b53_port) * sw_dev->ports, + GFP_KERNEL); + if (!dev->ports) + return -ENOMEM; + + dev->vlans = devm_kzalloc(dev->dev, + sizeof(struct b53_vlan) * sw_dev->vlans, + GFP_KERNEL); + if (!dev->vlans) + return -ENOMEM; + + dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); + if (!dev->buf) + return -ENOMEM; + + dev->reset_gpio = b53_switch_get_reset_gpio(dev); + if (dev->reset_gpio >= 0) { + ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, + GPIOF_OUT_INIT_HIGH, "robo_reset"); + if (ret) + return ret; + } + + return b53_switch_reset(dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv) +{ + struct b53_device *dev; + + dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->dev = base; + dev->ops = ops; + dev->priv = priv; + mutex_init(&dev->reg_mutex); + + return dev; +} +EXPORT_SYMBOL(b53_switch_alloc); + +int b53_switch_detect(struct b53_device *dev) +{ + u32 id32; + u16 tmp; + u8 id8; + int ret; + + ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); + if (ret) + return ret; + + switch (id8) { + case 0: + /* + * BCM5325 and BCM5365 do not have this register so reads + * return 0. But the read operation did succeed, so assume + * this is one of them. + * + * Next check if we can write to the 5325's VTA register; for + * 5365 it is read only. + */ + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); + b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); + + if (tmp == 0xf) + dev->chip_id = BCM5325_DEVICE_ID; + else + dev->chip_id = BCM5365_DEVICE_ID; + break; + case BCM5395_DEVICE_ID: + case BCM5397_DEVICE_ID: + case BCM5398_DEVICE_ID: + dev->chip_id = id8; + break; + default: + ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); + if (ret) + return ret; + + switch (id32) { + case BCM53115_DEVICE_ID: + case BCM53125_DEVICE_ID: + case BCM53128_DEVICE_ID: + case BCM53010_DEVICE_ID: + case BCM53011_DEVICE_ID: + case BCM53012_DEVICE_ID: + case BCM53018_DEVICE_ID: + case BCM53019_DEVICE_ID: + dev->chip_id = id32; + break; + default: + pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", + id8, id32); + return -ENODEV; + } + } + + if (dev->chip_id == BCM5325_DEVICE_ID) + return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, + &dev->core_rev); + else + return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, + &dev->core_rev); +} +EXPORT_SYMBOL(b53_switch_detect); + +int b53_switch_register(struct b53_device *dev) +{ + int ret; + + if (dev->pdata) { + dev->chip_id = dev->pdata->chip_id; + dev->enabled_ports = dev->pdata->enabled_ports; + dev->sw_dev.alias = dev->pdata->alias; + } + + if (!dev->chip_id && b53_switch_detect(dev)) + return -EINVAL; + + ret = b53_switch_init(dev); + if (ret) + return ret; + + pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); + + return register_switch(&dev->sw_dev, NULL); +} +EXPORT_SYMBOL(b53_switch_register); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 switch library"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mdio.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mdio.c new file mode 100644 index 0000000000..75bb4d941c --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mdio.c @@ -0,0 +1,436 @@ +/* + * B53 register access through MII registers + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "b53_priv.h" + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +/* MII registers */ +#define REG_MII_PAGE 0x10 /* MII Page register */ +#define REG_MII_ADDR 0x11 /* MII Address register */ +#define REG_MII_DATA0 0x18 /* MII Data register 0 */ +#define REG_MII_DATA1 0x19 /* MII Data register 1 */ +#define REG_MII_DATA2 0x1a /* MII Data register 2 */ +#define REG_MII_DATA3 0x1b /* MII Data register 3 */ + +#define REG_MII_PAGE_ENABLE BIT(0) +#define REG_MII_ADDR_WRITE BIT(0) +#define REG_MII_ADDR_READ BIT(1) + +static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) +{ + int i; + u16 v; + int ret; + struct mii_bus *bus = dev->priv; + + if (dev->current_page != page) { + /* set page number */ + v = (page << 8) | REG_MII_PAGE_ENABLE; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); + if (ret) + return ret; + dev->current_page = page; + } + + /* set register address */ + v = (reg << 8) | op; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); + if (ret) + return ret; + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); + if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; + + return 0; +} + +static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + + return 0; +} + +static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; + + return 0; +} + +static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 2; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 3; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u32 temp = value; + + for (i = 0; i < 2; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 3; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 4; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg, + u16 *value) +{ + struct mii_bus *bus = dev->priv; + + *value = mdiobus_read(bus, addr, reg); + + return 0; +} + +static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + + return mdiobus_write(bus, addr, reg, value); +} + +static struct b53_io_ops b53_mdio_ops = { + .read8 = b53_mdio_read8, + .read16 = b53_mdio_read16, + .read32 = b53_mdio_read32, + .read48 = b53_mdio_read48, + .read64 = b53_mdio_read64, + .write8 = b53_mdio_write8, + .write16 = b53_mdio_write16, + .write32 = b53_mdio_write32, + .write48 = b53_mdio_write48, + .write64 = b53_mdio_write64, + .phy_read16 = b53_mdio_phy_read16, + .phy_write16 = b53_mdio_phy_write16, +}; + +static int b53_phy_probe(struct phy_device *phydev) +{ + struct b53_device dev; + int ret; + + /* allow the generic phy driver to take over */ + if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0) + return -ENODEV; + + dev.current_page = 0xff; + dev.priv = phydev->mdio.bus; + dev.ops = &b53_mdio_ops; + dev.pdata = NULL; + mutex_init(&dev.reg_mutex); + + ret = b53_switch_detect(&dev); + if (ret) + return ret; + + if (is5325(&dev) || is5365(&dev)) + phydev->supported = SUPPORTED_100baseT_Full; + else + phydev->supported = SUPPORTED_1000baseT_Full; + + phydev->advertising = phydev->supported; + + return 0; +} + +static int b53_phy_config_init(struct phy_device *phydev) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus); + if (!dev) + return -ENOMEM; + + /* we don't use page 0xff, so force a page set */ + dev->current_page = 0xff; + /* force the ethX as alias */ + dev->sw_dev.alias = phydev->attached_dev->name; + + ret = b53_switch_register(dev); + if (ret) { + dev_err(dev->dev, "failed to register switch: %i\n", ret); + return ret; + } + + phydev->priv = dev; + + return 0; +} + +static void b53_phy_remove(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (!priv) + return; + + b53_switch_remove(priv); + + phydev->priv = NULL; +} + +static int b53_phy_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int b53_phy_read_status(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (is5325(priv) || is5365(priv)) + phydev->speed = 100; + else + phydev->speed = 1000; + + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + phydev->state = PHY_RUNNING; + + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* BCM5325, BCM539x */ +static struct phy_driver b53_phy_driver_id1 = { + .phy_id = 0x0143bc00, + .name = "Broadcom B53 (1)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM53125, BCM53128 */ +static struct phy_driver b53_phy_driver_id2 = { + .phy_id = 0x03625c00, + .name = "Broadcom B53 (2)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM5365 */ +static struct phy_driver b53_phy_driver_id3 = { + .phy_id = 0x00406000, + .name = "Broadcom B53 (3)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +int __init b53_phy_driver_register(void) +{ + int ret; + + ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE); + if (ret) + return ret; + + ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE); + if (ret) + goto err1; + + ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE); + if (!ret) + return 0; + + phy_driver_unregister(&b53_phy_driver_id2); +err1: + phy_driver_unregister(&b53_phy_driver_id1); + return ret; +} + +void __exit b53_phy_driver_unregister(void) +{ + phy_driver_unregister(&b53_phy_driver_id3); + phy_driver_unregister(&b53_phy_driver_id2); + phy_driver_unregister(&b53_phy_driver_id1); +} + +module_init(b53_phy_driver_register); +module_exit(b53_phy_driver_unregister); + +MODULE_DESCRIPTION("B53 MDIO access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mmap.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mmap.c new file mode 100644 index 0000000000..ab1895e9b5 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_mmap.c @@ -0,0 +1,241 @@ +/* + * B53 register access through memory mapped registers + * + * Copyright (C) 2012-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + + *val = readb(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readw_be(regs + (page << 8) + reg); + else + *val = readw(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readl_be(regs + (page << 8) + reg); + else + *val = readl(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u16 lo; + u32 hi; + + b53_mmap_read16(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 2, &hi); + + *val = ((u64)hi << 16) | lo; + } else { + u32 lo; + u16 hi; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read16(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + } + + return 0; +} + +static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u32 hi, lo; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + + return 0; +} + +static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + + writeb(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writew_be(value, regs + (page << 8) + reg); + else + writew(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writel_be(value, regs + (page << 8) + reg); + else + writel(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u32 hi = (u32)(value >> 16); + u16 lo = (u16)value; + + b53_mmap_write16(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 2, hi); + } else { + u16 hi = (u16)(value >> 32); + u32 lo = (u32)value; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write16(dev, page, reg + 4, hi); + } + + return 0; +} + +static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u32 hi, lo; + + hi = (u32)(value >> 32); + lo = (u32)value; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 4, hi); + + return 0; +} + +static struct b53_io_ops b53_mmap_ops = { + .read8 = b53_mmap_read8, + .read16 = b53_mmap_read16, + .read32 = b53_mmap_read32, + .read48 = b53_mmap_read48, + .read64 = b53_mmap_read64, + .write8 = b53_mmap_write8, + .write16 = b53_mmap_write16, + .write32 = b53_mmap_write32, + .write48 = b53_mmap_write48, + .write64 = b53_mmap_write64, +}; + +static int b53_mmap_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_mmap_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_mmap_driver = { + .probe = b53_mmap_probe, + .remove = b53_mmap_remove, + .driver = { + .name = "b53-switch", + }, +}; + +module_platform_driver(b53_mmap_driver); +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 MMAP access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_phy_fixup.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_phy_fixup.c new file mode 100644 index 0000000000..e2f8a39ab6 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_phy_fixup.c @@ -0,0 +1,55 @@ +/* + * B53 PHY Fixup call + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +#define B53_BRCM_OUI_1 0x0143bc00 +#define B53_BRCM_OUI_2 0x03625c00 +#define B53_BRCM_OUI_3 0x00406000 + +static int b53_phy_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u32 phy_id; + + if (dev->mdio.addr != B53_PSEUDO_PHY) + return 0; + + /* read the first port's id */ + phy_id = mdiobus_read(bus, 0, 2) << 16; + phy_id |= mdiobus_read(bus, 0, 3); + + if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) { + dev->phy_id = phy_id; + } + + return 0; +} + +int __init b53_phy_fixup_register(void) +{ + return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup); +} + +subsys_initcall(b53_phy_fixup_register); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_priv.h b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_priv.h new file mode 100644 index 0000000000..a9296c9421 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_priv.h @@ -0,0 +1,341 @@ +/* + * B53 common definitions + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_PRIV_H +#define __B53_PRIV_H + +#include +#include +#include + +struct b53_device; + +struct b53_io_ops { + int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); + int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); + int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); + int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); + int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); + int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); + int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value); +}; + +enum { + BCM5325_DEVICE_ID = 0x25, + BCM5365_DEVICE_ID = 0x65, + BCM5395_DEVICE_ID = 0x95, + BCM5397_DEVICE_ID = 0x97, + BCM5398_DEVICE_ID = 0x98, + BCM53115_DEVICE_ID = 0x53115, + BCM53125_DEVICE_ID = 0x53125, + BCM53128_DEVICE_ID = 0x53128, + BCM63XX_DEVICE_ID = 0x6300, + BCM53010_DEVICE_ID = 0x53010, + BCM53011_DEVICE_ID = 0x53011, + BCM53012_DEVICE_ID = 0x53012, + BCM53018_DEVICE_ID = 0x53018, + BCM53019_DEVICE_ID = 0x53019, +}; + +#define B53_N_PORTS 9 +#define B53_N_PORTS_25 6 + +struct b53_vlan { + unsigned int members:B53_N_PORTS; + unsigned int untag:B53_N_PORTS; +}; + +struct b53_port { + unsigned int pvid:12; +}; + +struct b53_device { + struct switch_dev sw_dev; + struct b53_platform_data *pdata; + + struct mutex reg_mutex; + const struct b53_io_ops *ops; + + /* chip specific data */ + u32 chip_id; + u8 core_rev; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + int reset_gpio; + + /* used ports mask */ + u16 enabled_ports; + + /* connect specific data */ + u8 current_page; + struct device *dev; + void *priv; + + /* run time configuration */ + unsigned enable_vlan:1; + unsigned enable_jumbo:1; + unsigned allow_vid_4095:1; + + struct b53_port *ports; + struct b53_vlan *vlans; + + char *buf; +}; + +#define b53_for_each_port(dev, i) \ + for (i = 0; i < B53_N_PORTS; i++) \ + if (dev->enabled_ports & BIT(i)) + + + +static inline int is5325(struct b53_device *dev) +{ + return dev->chip_id == BCM5325_DEVICE_ID; +} + +static inline int is5365(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + return dev->chip_id == BCM5365_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5397_98(struct b53_device *dev) +{ + return dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is539x(struct b53_device *dev) +{ + return dev->chip_id == BCM5395_DEVICE_ID || + dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is531x5(struct b53_device *dev) +{ + return dev->chip_id == BCM53115_DEVICE_ID || + dev->chip_id == BCM53125_DEVICE_ID || + dev->chip_id == BCM53128_DEVICE_ID; +} + +static inline int is63xx(struct b53_device *dev) +{ +#ifdef CONFIG_BCM63XX + return dev->chip_id == BCM63XX_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5301x(struct b53_device *dev) +{ + return dev->chip_id == BCM53010_DEVICE_ID || + dev->chip_id == BCM53011_DEVICE_ID || + dev->chip_id == BCM53012_DEVICE_ID || + dev->chip_id == BCM53018_DEVICE_ID || + dev->chip_id == BCM53019_DEVICE_ID; +} + +#define B53_CPU_PORT_25 5 +#define B53_CPU_PORT 8 + +static inline int is_cpu_port(struct b53_device *dev, int port) +{ + return dev->sw_dev.cpu_port == port; +} + +static inline int is_imp_port(struct b53_device *dev, int port) +{ + if (is5325(dev) || is5365(dev)) + return port == B53_CPU_PORT_25; + else + return port == B53_CPU_PORT; +} + +static inline struct b53_device *sw_to_b53(struct switch_dev *sw) +{ + return container_of(sw, struct b53_device, sw_dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv); + +int b53_switch_detect(struct b53_device *dev); + +int b53_switch_register(struct b53_device *dev); + +static inline void b53_switch_remove(struct b53_device *dev) +{ + unregister_switch(&dev->sw_dev); +} + +static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read8(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read16(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read32(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read48(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read64(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write8(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write16(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write32(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write48(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write64(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +#ifdef CONFIG_BCM47XX +#include +#endif + +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) +#include +#endif +static inline int b53_switch_get_reset_gpio(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + enum bcm47xx_board board = bcm47xx_board_get(); + + switch (board) { + case BCM47XX_BOARD_LINKSYS_WRT300NV11: + case BCM47XX_BOARD_LINKSYS_WRT310NV1: + return 8; + default: + break; + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + return bcm47xx_nvram_gpio_pin("robo_reset"); +#else + return -ENOENT; +#endif +} + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_regs.h b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_regs.h new file mode 100644 index 0000000000..f0bf6744a7 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_regs.h @@ -0,0 +1,348 @@ +/* + * B53 register definitions + * + * Copyright (C) 2004 Broadcom Corporation + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_REGS_H +#define __B53_REGS_H + +/* Management Port (SMP) Page offsets */ +#define B53_CTRL_PAGE 0x00 /* Control */ +#define B53_STAT_PAGE 0x01 /* Status */ +#define B53_MGMT_PAGE 0x02 /* Management Mode */ +#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ +#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ +#define B53_ARLIO_PAGE 0x05 /* ARL Access */ +#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ +#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ + +/* PHY Registers */ +#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */ +#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ +#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ + +/* MIB registers */ +#define B53_MIB_PAGE(i) (0x20 + (i)) + +/* Quality of Service (QoS) Registers */ +#define B53_QOS_PAGE 0x30 + +/* Port VLAN Page */ +#define B53_PVLAN_PAGE 0x31 + +/* VLAN Registers */ +#define B53_VLAN_PAGE 0x34 + +/* Jumbo Frame Registers */ +#define B53_JUMBO_PAGE 0x40 + +/* CFP Configuration Registers Page */ +#define B53_CFP_PAGE 0xa1 + +/************************************************************************* + * Control Page registers + *************************************************************************/ + +/* Port Control Register (8 bit) */ +#define B53_PORT_CTRL(i) (0x00 + (i)) +#define PORT_CTRL_RX_DISABLE BIT(0) +#define PORT_CTRL_TX_DISABLE BIT(1) +#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ +#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ +#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ +#define PORT_CTRL_STP_STATE_S 5 +#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S) + +/* SMP Control Register (8 bit) */ +#define B53_SMP_CTRL 0x0a + +/* Switch Mode Control Register (8 bit) */ +#define B53_SWITCH_MODE 0x0b +#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ +#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ + +/* IMP Port state override register (8 bit) */ +#define B53_PORT_OVERRIDE_CTRL 0x0e +#define PORT_OVERRIDE_LINK BIT(0) +#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define PORT_OVERRIDE_SPEED_S 2 +#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ +#define PORT_OVERRIDE_RX_FLOW BIT(4) +#define PORT_OVERRIDE_TX_FLOW BIT(5) +#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */ +#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ + +/* Power-down mode control */ +#define B53_PD_MODE_CTRL_25 0x0f + +/* IP Multicast control (8 bit) */ +#define B53_IP_MULTICAST_CTRL 0x21 +#define B53_IPMC_FWD_EN BIT(1) +#define B53_UC_FWD_EN BIT(6) +#define B53_MC_FWD_EN BIT(7) + +/* (16 bit) */ +#define B53_UC_FLOOD_MASK 0x32 +#define B53_MC_FLOOD_MASK 0x34 +#define B53_IPMC_FLOOD_MASK 0x36 + +/* + * Override Ports 0-7 State on devices with xMII interfaces (8 bit) + * + * For port 8 still use B53_PORT_OVERRIDE_CTRL + * Please note that not all ports are available on every hardware, e.g. BCM5301X + * don't include overriding port 6, BCM63xx also have some limitations. + */ +#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i)) +#define GMII_PO_LINK BIT(0) +#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define GMII_PO_SPEED_S 2 +#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S) +#define GMII_PO_RX_FLOW BIT(4) +#define GMII_PO_TX_FLOW BIT(5) +#define GMII_PO_EN BIT(6) /* Use the register contents */ +#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */ + +/* Software reset register (8 bit) */ +#define B53_SOFTRESET 0x79 + +/* Fast Aging Control register (8 bit) */ +#define B53_FAST_AGE_CTRL 0x88 +#define FAST_AGE_STATIC BIT(0) +#define FAST_AGE_DYNAMIC BIT(1) +#define FAST_AGE_PORT BIT(2) +#define FAST_AGE_VLAN BIT(3) +#define FAST_AGE_STP BIT(4) +#define FAST_AGE_MC BIT(5) +#define FAST_AGE_DONE BIT(7) + +/************************************************************************* + * Status Page registers + *************************************************************************/ + +/* Link Status Summary Register (16bit) */ +#define B53_LINK_STAT 0x00 + +/* Link Status Change Register (16 bit) */ +#define B53_LINK_STAT_CHANGE 0x02 + +/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ +#define B53_SPEED_STAT 0x04 +#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) +#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) +#define SPEED_STAT_10M 0 +#define SPEED_STAT_100M 1 +#define SPEED_STAT_1000M 2 + +/* Duplex Status Summary (16 bit) */ +#define B53_DUPLEX_STAT_FE 0x06 +#define B53_DUPLEX_STAT_GE 0x08 +#define B53_DUPLEX_STAT_63XX 0x0c + +/* Revision ID register for BCM5325 */ +#define B53_REV_ID_25 0x50 + +/* Strap Value (48 bit) */ +#define B53_STRAP_VALUE 0x70 +#define SV_GMII_CTRL_115 BIT(27) + +/************************************************************************* + * Management Mode Page Registers + *************************************************************************/ + +/* Global Management Config Register (8 bit) */ +#define B53_GLOBAL_CONFIG 0x00 +#define GC_RESET_MIB 0x01 +#define GC_RX_BPDU_EN 0x02 +#define GC_MIB_AC_HDR_EN 0x10 +#define GC_MIB_AC_EN 0x20 +#define GC_FRM_MGMT_PORT_M 0xC0 +#define GC_FRM_MGMT_PORT_04 0x00 +#define GC_FRM_MGMT_PORT_MII 0x80 + +/* Broadcom Header control register (8 bit) */ +#define B53_BRCM_HDR 0x03 +#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */ +#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */ + +/* Device ID register (8 or 32 bit) */ +#define B53_DEVICE_ID 0x30 + +/* Revision ID register (8 bit) */ +#define B53_REV_ID 0x40 + +/************************************************************************* + * ARL Access Page Registers + *************************************************************************/ + +/* VLAN Table Access Register (8 bit) */ +#define B53_VT_ACCESS 0x80 +#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ +#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ +#define VTA_CMD_WRITE 0 +#define VTA_CMD_READ 1 +#define VTA_CMD_CLEAR 2 +#define VTA_START_CMD BIT(7) + +/* VLAN Table Index Register (16 bit) */ +#define B53_VT_INDEX 0x81 +#define B53_VT_INDEX_9798 0x61 +#define B53_VT_INDEX_63XX 0x62 + +/* VLAN Table Entry Register (32 bit) */ +#define B53_VT_ENTRY 0x83 +#define B53_VT_ENTRY_9798 0x63 +#define B53_VT_ENTRY_63XX 0x64 +#define VTE_MEMBERS 0x1ff +#define VTE_UNTAG_S 9 +#define VTE_UNTAG (0x1ff << 9) + +/************************************************************************* + * Port VLAN Registers + *************************************************************************/ + +/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ +#define B53_PVLAN_PORT_MASK(i) ((i) * 2) + +/************************************************************************* + * 802.1Q Page Registers + *************************************************************************/ + +/* Global QoS Control (8 bit) */ +#define B53_QOS_GLOBAL_CTL 0x00 + +/* Enable 802.1Q for individual Ports (16 bit) */ +#define B53_802_1P_EN 0x04 + +/************************************************************************* + * VLAN Page Registers + *************************************************************************/ + +/* VLAN Control 0 (8 bit) */ +#define B53_VLAN_CTRL0 0x00 +#define VC0_8021PF_CTRL_MASK 0x3 +#define VC0_8021PF_CTRL_NONE 0x0 +#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021PF_CTRL_CHANGE_VID 0x2 +#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 +#define VC0_8021QF_CTRL_MASK 0xc +#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021QF_CTRL_CHANGE_VID 0x2 +#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 +#define VC0_RESERVED_1 BIT(1) +#define VC0_DROP_VID_MISS BIT(4) +#define VC0_VID_HASH_VID BIT(5) +#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ +#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ + +/* VLAN Control 1 (8 bit) */ +#define B53_VLAN_CTRL1 0x01 +#define VC1_RX_MCST_TAG_EN BIT(1) +#define VC1_RX_MCST_FWD_EN BIT(2) +#define VC1_RX_MCST_UNTAG_EN BIT(3) + +/* VLAN Control 2 (8 bit) */ +#define B53_VLAN_CTRL2 0x02 + +/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ +#define B53_VLAN_CTRL3 0x03 +#define B53_VLAN_CTRL3_63XX 0x04 +#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ +#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ + +/* VLAN Control 4 (8 bit) */ +#define B53_VLAN_CTRL4 0x05 +#define B53_VLAN_CTRL4_25 0x04 +#define B53_VLAN_CTRL4_63XX 0x06 +#define VC4_ING_VID_CHECK_S 6 +#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) +#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ +#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ +#define VC4_NO_ING_VID_CHK 2 /* do not check */ +#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ + +/* VLAN Control 5 (8 bit) */ +#define B53_VLAN_CTRL5 0x06 +#define B53_VLAN_CTRL5_25 0x05 +#define B53_VLAN_CTRL5_63XX 0x07 +#define VC5_VID_FFF_EN BIT(2) +#define VC5_DROP_VTABLE_MISS BIT(3) + +/* VLAN Control 6 (8 bit) */ +#define B53_VLAN_CTRL6 0x07 +#define B53_VLAN_CTRL6_63XX 0x08 + +/* VLAN Table Access Register (16 bit) */ +#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ +#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ +#define VTA_VID_LOW_MASK_25 0xf +#define VTA_VID_LOW_MASK_65 0xff +#define VTA_VID_HIGH_S_25 4 +#define VTA_VID_HIGH_S_65 8 +#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) +#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) +#define VTA_RW_STATE BIT(12) +#define VTA_RW_STATE_RD 0 +#define VTA_RW_STATE_WR BIT(12) +#define VTA_RW_OP_EN BIT(13) + +/* VLAN Read/Write Registers for (16/32 bit) */ +#define B53_VLAN_WRITE_25 0x08 +#define B53_VLAN_WRITE_65 0x0a +#define B53_VLAN_READ 0x0c +#define VA_MEMBER_MASK 0x3f +#define VA_UNTAG_S_25 6 +#define VA_UNTAG_MASK_25 0x3f +#define VA_UNTAG_S_65 7 +#define VA_UNTAG_MASK_65 0x1f +#define VA_VID_HIGH_S 12 +#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) +#define VA_VALID_25 BIT(20) +#define VA_VALID_25_R4 BIT(24) +#define VA_VALID_65 BIT(14) + +/* VLAN Port Default Tag (16 bit) */ +#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) + +/************************************************************************* + * Jumbo Frame Page Registers + *************************************************************************/ + +/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ +#define B53_JUMBO_PORT_MASK 0x01 +#define B53_JUMBO_PORT_MASK_63XX 0x04 +#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ + +/* Good Frame Max Size without 802.1Q TAG (16 bit) */ +#define B53_JUMBO_MAX_SIZE 0x05 +#define B53_JUMBO_MAX_SIZE_63XX 0x08 +#define JMS_MIN_SIZE 1518 +#define JMS_MAX_SIZE 9724 + +/************************************************************************* + * CFP Configuration Page Registers + *************************************************************************/ + +/* CFP Control Register with ports map (8 bit) */ +#define B53_CFP_CTRL 0x00 + +#endif /* !__B53_REGS_H */ diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_spi.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_spi.c new file mode 100644 index 0000000000..efc8f7ee89 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_spi.c @@ -0,0 +1,344 @@ +/* + * B53 register access through SPI + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +#define B53_SPI_DATA 0xf0 + +#define B53_SPI_STATUS 0xfe +#define B53_SPI_CMD_SPIF BIT(7) +#define B53_SPI_CMD_RACK BIT(5) + +#define B53_SPI_CMD_READ 0x00 +#define B53_SPI_CMD_WRITE 0x01 +#define B53_SPI_CMD_NORMAL 0x60 +#define B53_SPI_CMD_FAST 0x10 + +#define B53_SPI_PAGE_SELECT 0xff + +static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, + unsigned len) +{ + u8 txbuf[2]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; + txbuf[1] = reg; + + return spi_write_then_read(spi, txbuf, 2, val, len); +} + +static inline int b53_spi_clear_status(struct spi_device *spi) +{ + unsigned int i; + u8 rxbuf; + int ret; + + for (i = 0; i < 10; i++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (!(rxbuf & B53_SPI_CMD_SPIF)) + break; + + mdelay(1); + } + + if (i == 10) + return -EIO; + + return 0; +} + +static inline int b53_spi_set_page(struct spi_device *spi, u8 page) +{ + u8 txbuf[3]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = B53_SPI_PAGE_SELECT; + txbuf[2] = page; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) +{ + int ret = b53_spi_clear_status(spi); + + if (ret) + return ret; + + return b53_spi_set_page(spi, page); +} + +static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) +{ + u8 rxbuf; + int retry_count; + int ret; + + ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); + if (ret) + return ret; + + for (retry_count = 0; retry_count < 10; retry_count++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (rxbuf & B53_SPI_CMD_RACK) + break; + + mdelay(1); + } + + if (retry_count == 10) + return -EIO; + + return 0; +} + +static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, + unsigned len) +{ + struct spi_device *spi = dev->priv; + int ret; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + ret = b53_spi_prepare_reg_read(spi, reg); + if (ret) + return ret; + + return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); +} + +static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + return b53_spi_read(dev, page, reg, val, 1); +} + +static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); + + if (!ret) + *val = le16_to_cpu(*val); + + return ret; +} + +static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); + + if (!ret) + *val = le32_to_cpu(*val); + + return ret; +} + +static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + *val = 0; + ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); + + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[3]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + txbuf[2] = value; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[4]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le16(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[6]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le32(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf) - 2); +} + +static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static struct b53_io_ops b53_spi_ops = { + .read8 = b53_spi_read8, + .read16 = b53_spi_read16, + .read32 = b53_spi_read32, + .read48 = b53_spi_read48, + .read64 = b53_spi_read64, + .write8 = b53_spi_write8, + .write16 = b53_spi_write16, + .write32 = b53_spi_write32, + .write48 = b53_spi_write48, + .write64 = b53_spi_write64, +}; + +static int b53_spi_probe(struct spi_device *spi) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); + if (!dev) + return -ENOMEM; + + if (spi->dev.platform_data) + dev->pdata = spi->dev.platform_data; + + ret = b53_switch_register(dev); + if (ret) + return ret; + + spi_set_drvdata(spi, dev); + + return 0; +} + +static int b53_spi_remove(struct spi_device *spi) +{ + struct b53_device *dev = spi_get_drvdata(spi); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static const struct of_device_id b53_of_match[] = { + { .compatible = "brcm,bcm5325" }, + { .compatible = "brcm,bcm53115" }, + { .compatible = "brcm,bcm53125" }, + { .compatible = "brcm,bcm53128" }, + { .compatible = "brcm,bcm5365" }, + { .compatible = "brcm,bcm5395" }, + { .compatible = "brcm,bcm5397" }, + { .compatible = "brcm,bcm5398" }, + { /* sentinel */ }, +}; + +static struct spi_driver b53_spi_driver = { + .driver = { + .name = "b53-switch", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = b53_of_match, + }, + .probe = b53_spi_probe, + .remove = b53_spi_remove, +}; + +module_spi_driver(b53_spi_driver); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 SPI access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_srab.c b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_srab.c new file mode 100644 index 0000000000..012daa3a51 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/b53/b53_srab.c @@ -0,0 +1,378 @@ +/* + * B53 register access through Switch Register Access Bridge Registers + * + * Copyright (C) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +/* command and status register of the SRAB */ +#define B53_SRAB_CMDSTAT 0x2c +#define B53_SRAB_CMDSTAT_RST BIT(2) +#define B53_SRAB_CMDSTAT_WRITE BIT(1) +#define B53_SRAB_CMDSTAT_GORDYN BIT(0) +#define B53_SRAB_CMDSTAT_PAGE 24 +#define B53_SRAB_CMDSTAT_REG 16 + +/* high order word of write data to switch registe */ +#define B53_SRAB_WD_H 0x30 + +/* low order word of write data to switch registe */ +#define B53_SRAB_WD_L 0x34 + +/* high order word of read data from switch register */ +#define B53_SRAB_RD_H 0x38 + +/* low order word of read data from switch register */ +#define B53_SRAB_RD_L 0x3c + +/* command and status register of the SRAB */ +#define B53_SRAB_CTRLS 0x40 +#define B53_SRAB_CTRLS_RCAREQ BIT(3) +#define B53_SRAB_CTRLS_RCAGNT BIT(4) +#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) + +/* the register captures interrupt pulses from the switch */ +#define B53_SRAB_INTR 0x44 + +static int b53_srab_request_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + int i; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls |= B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); + + for (i = 0; i < 20; i++) { + ctrls = readl(regs + B53_SRAB_CTRLS); + if (ctrls & B53_SRAB_CTRLS_RCAGNT) + break; + usleep_range(10, 100); + } + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static void b53_srab_release_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls &= ~B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); +} + +static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) +{ + int i; + u32 cmdstat; + u8 __iomem *regs = dev->priv; + + /* set register address */ + cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | + (reg << B53_SRAB_CMDSTAT_REG) | + B53_SRAB_CMDSTAT_GORDYN | + op; + writel(cmdstat, regs + B53_SRAB_CMDSTAT); + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + cmdstat = readl(regs + B53_SRAB_CMDSTAT); + if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xffff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u16)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u32)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static struct b53_io_ops b53_srab_ops = { + .read8 = b53_srab_read8, + .read16 = b53_srab_read16, + .read32 = b53_srab_read32, + .read48 = b53_srab_read48, + .read64 = b53_srab_read64, + .write8 = b53_srab_write8, + .write16 = b53_srab_write16, + .write32 = b53_srab_write32, + .write48 = b53_srab_write48, + .write64 = b53_srab_write64, +}; + +static int b53_srab_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_srab_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_srab_driver = { + .probe = b53_srab_probe, + .remove = b53_srab_remove, + .driver = { + .name = "b53-srab-switch", + }, +}; + +module_platform_driver(b53_srab_driver); +MODULE_AUTHOR("Hauke Mehrtens "); +MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/ip17xx.c b/target/linux/generic/files-3.18/drivers/net/phy/ip17xx.c new file mode 100644 index 0000000000..85a9617a5d --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/ip17xx.c @@ -0,0 +1,1377 @@ +/* + * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family + * + * Copyright (C) 2008 Patrick Horn + * Copyright (C) 2008, 2010 Martin Mares + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_VLANS 16 +#define MAX_PORTS 9 +#undef DUMP_MII_IO + +typedef struct ip17xx_reg { + u16 p; // phy + u16 m; // mii +} reg; +typedef char bitnum; + +#define NOTSUPPORTED {-1,-1} + +#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) + +struct ip17xx_state; + +/*********** CONSTANTS ***********/ +struct register_mappings { + char *NAME; + u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. + bitnum NUM_PORTS; + bitnum CPU_PORT; + +/* The default VLAN for each port. + Default: 0x0001 for Ports 0,1,2,3 + 0x0002 for Ports 4,5 */ + reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; + +/* These ports are tagged. + Default: 0x00 */ + reg ADD_TAG_REG; + reg REMOVE_TAG_REG; + bitnum ADD_TAG_BIT[MAX_PORTS]; +/* These ports are untagged. + Default: 0x00 (i.e. do not alter any VLAN tags...) + Maybe set to 0 if user disables VLANs. */ + bitnum REMOVE_TAG_BIT[MAX_PORTS]; + +/* Port M and Port N are on the same VLAN. + Default: All ports on all VLANs. */ +// Use register {29, 19+N/2} + reg VLAN_LOOKUP_REG; +// Port 5 uses register {30, 18} but same as odd bits. + reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. + bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; + bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; + +/* This VLAN corresponds to which ports. + Default: 0x2f,0x30,0x3f,0x3f... */ + reg TAG_VLAN_MASK_REG; + bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; + bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; + + int RESET_VAL; + reg RESET_REG; + + reg MODE_REG; + int MODE_VAL; + +/* General flags */ + reg ROUTER_CONTROL_REG; + reg VLAN_CONTROL_REG; + bitnum TAG_VLAN_BIT; + bitnum ROUTER_EN_BIT; + bitnum NUMLAN_GROUPS_MAX; + bitnum NUMLAN_GROUPS_BIT; + + reg MII_REGISTER_EN; + bitnum MII_REGISTER_EN_BIT; + + // set to 1 for 178C, 0 for 175C. + bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. + + // Pointers to functions which manipulate hardware state + int (*update_state)(struct ip17xx_state *state); + int (*set_vlan_mode)(struct ip17xx_state *state); + int (*reset)(struct ip17xx_state *state); +}; + +static int ip175c_update_state(struct ip17xx_state *state); +static int ip175c_set_vlan_mode(struct ip17xx_state *state); +static int ip175c_reset(struct ip17xx_state *state); + +static const struct register_mappings IP178C = { + .NAME = "IP178C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, + {30,9},{30,10},{30,11}, + }, + + .ADD_TAG_REG = {30,12}, + .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, + .REMOVE_TAG_REG = {30,13}, + .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, + + .SIMPLE_VLAN_REGISTERS = 1, + + .VLAN_LOOKUP_REG = {31,0},// +N + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS + .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .TAG_VLAN_MASK_REG = {30,14}, // +N + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .RESET_VAL = 0x55AA, + .RESET_REG = {30,0}, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = {30,30}, + .ROUTER_EN_BIT = 11, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 8, // {0-2} + + .VLAN_CONTROL_REG = {30,13}, + .TAG_VLAN_BIT = 3, + + .CPU_PORT = 8, + .NUM_PORTS = 9, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175C = { + .NAME = "IP175C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {29,23}, + .REMOVE_TAG_REG = {29,23}, + .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = {29,19},// +N/2 + .VLAN_LOOKUP_REG_5 = {30,18}, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = {30,1}, // +N/2 + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, + + .RESET_VAL = 0x175C, + .RESET_REG = {30,0}, + .MODE_VAL = 0x175C, + .MODE_REG = {29,31}, + + .ROUTER_CONTROL_REG = {30,9}, + .ROUTER_EN_BIT = 3, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 0, // {0-2} + + .VLAN_CONTROL_REG = {30,9}, + .TAG_VLAN_BIT = 7, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175A = { + .NAME = "IP175A", + .MODEL_NO = 0x05, + .VLAN_DEFAULT_TAG_REG = { + {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {0,23}, + .REMOVE_TAG_REG = {0,23}, + .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + // Only programmable via EEPROM + .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, + .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + + .RESET_VAL = -1, + .RESET_REG = NOTSUPPORTED, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_MAX = -1, + .NUMLAN_GROUPS_BIT = -1, // {0-2} + + .NUM_PORTS = 5, + .CPU_PORT = 4, + + .MII_REGISTER_EN = {0, 18}, + .MII_REGISTER_EN_BIT = 7, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + + +static int ip175d_update_state(struct ip17xx_state *state); +static int ip175d_set_vlan_mode(struct ip17xx_state *state); +static int ip175d_reset(struct ip17xx_state *state); + +static const struct register_mappings IP175D = { + .NAME = "IP175D", + .MODEL_NO = 0x18, + + // The IP175D has a completely different interface, so we leave most + // of the registers undefined and switch to different code paths. + + .VLAN_DEFAULT_TAG_REG = { + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + }, + + .ADD_TAG_REG = NOTSUPPORTED, + .REMOVE_TAG_REG = NOTSUPPORTED, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = NOTSUPPORTED, + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .TAG_VLAN_MASK_REG = NOTSUPPORTED, + + .RESET_VAL = 0x175D, + .RESET_REG = {20,2}, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_BIT = -1, + + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175d_update_state, + .set_vlan_mode = ip175d_set_vlan_mode, + .reset = ip175d_reset, +}; + +struct ip17xx_state { + struct switch_dev dev; + struct mii_bus *mii_bus; + bool registered; + + int router_mode; // ROUTER_EN + int vlan_enabled; // TAG_VLAN_EN + struct port_state { + u16 pvid; + unsigned int shareports; + } ports[MAX_PORTS]; + unsigned int add_tag; + unsigned int remove_tag; + int num_vlans; + struct vlan_state { + unsigned int ports; + unsigned int tag; // VLAN tag (IP175D only) + } vlans[MAX_VLANS]; + const struct register_mappings *regs; + reg proc_mii; // phy/reg for the low level register access via swconfig + + char buf[80]; +}; + +#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) + +static int ip_phy_read(struct ip17xx_state *state, int port, int reg) +{ + int val = mdiobus_read(state->mii_bus, port, reg); + if (val < 0) + pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); +#ifdef DUMP_MII_IO + else + pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); +#endif + return val; +} + +static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) +{ + int err; + +#ifdef DUMP_MII_IO + pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); +#endif + err = mdiobus_write(state->mii_bus, port, reg, val); + if (err < 0) + pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); + return err; +} + +static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) +{ + int val = ip_phy_read(state, port, reg); + if (val < 0) + return 0; + return ip_phy_write(state, port, reg, (val & ~mask) | data); +} + +static int getPhy(struct ip17xx_state *state, reg mii) +{ + if (!REG_SUPP(mii)) + return -EFAULT; + return ip_phy_read(state, mii.p, mii.m); +} + +static int setPhy(struct ip17xx_state *state, reg mii, u16 value) +{ + int err; + + if (!REG_SUPP(mii)) + return -EFAULT; + err = ip_phy_write(state, mii.p, mii.m, value); + if (err < 0) + return err; + mdelay(2); + getPhy(state, mii); + return 0; +} + + +/** + * These two macros are to simplify the mapping of logical bits to the bits in hardware. + * NOTE: these macros will return if there is an error! + */ + +#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + (bits) = 0; \ + for (i = 0; i < MAX_PORTS; i++) { \ + if ((bit_lookup)[i] == -1) continue; \ + if (val & (1<<(bit_lookup)[i])) \ + (bits) |= (1<>i)<<(bit_lookup)[i]); \ + } \ + val = setPhy((state), (addr), val); \ + if (val < 0) \ + return val; \ + } while (0) + + +static int get_model(struct ip17xx_state *state) +{ + int id1, id2; + int oui_id, model_no, rev_no, chip_no; + + id1 = ip_phy_read(state, 0, 2); + id2 = ip_phy_read(state, 0, 3); + oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); + model_no = (id2 >> 4) & 0x3f; + rev_no = id2 & 0xf; + pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); + + if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway + return -ENODEV; + + if (model_no == IP175A.MODEL_NO) { + state->regs = &IP175A; + } else if (model_no == IP175C.MODEL_NO) { + /* + * Several models share the same model_no: + * 178C has more PHYs, so we try whether the device responds to a read from PHY5 + * 175D has a new chip ID register + * 175C has neither + */ + if (ip_phy_read(state, 5, 2) == 0x0243) { + state->regs = &IP178C; + } else { + chip_no = ip_phy_read(state, 20, 0); + pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); + if (chip_no == 0x175d) { + state->regs = &IP175D; + } else { + state->regs = &IP175C; + } + } + } else { + pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); + return -EPERM; + } + return 0; +} + +/*** Low-level functions for the older models ***/ + +/** Only set vlan and router flags in the switch **/ +static int ip175c_set_flags(struct ip17xx_state *state) +{ + int val; + + if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { + return 0; + } + + val = getPhy(state, state->regs->ROUTER_CONTROL_REG); + if (val < 0) { + return val; + } + if (state->regs->ROUTER_EN_BIT >= 0) { + if (state->router_mode) { + val |= (1<regs->ROUTER_EN_BIT); + } else { + val &= (~(1<regs->ROUTER_EN_BIT)); + } + } + if (state->regs->TAG_VLAN_BIT >= 0) { + if (state->vlan_enabled) { + val |= (1<regs->TAG_VLAN_BIT); + } else { + val &= (~(1<regs->TAG_VLAN_BIT)); + } + } + if (state->regs->NUMLAN_GROUPS_BIT >= 0) { + val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<regs->NUMLAN_GROUPS_BIT)); + if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { + val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; + } else if (state->num_vlans >= 1) { + val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; + } + } + return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); +} + +/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ +static int ip175c_set_state(struct ip17xx_state *state) +{ + int j; + int i; + SET_PORT_BITS(state, state->add_tag, + state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); + SET_PORT_BITS(state, state->remove_tag, + state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); + + if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { + for (j=0; jregs->NUM_PORTS; j++) { + reg addr; + const bitnum *bit_lookup = (j%2==0)? + state->regs->VLAN_LOOKUP_EVEN_BIT: + state->regs->VLAN_LOOKUP_ODD_BIT; + + addr = state->regs->VLAN_LOOKUP_REG; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + switch (j) { + case 0: + case 1: + break; + case 2: + case 3: + addr.m+=1; + break; + case 4: + addr.m+=2; + break; + case 5: + addr = state->regs->VLAN_LOOKUP_REG_5; + break; + default: + addr.m = -1; // shouldn't get here, but... + break; + } + } + //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); + if (REG_SUPP(addr)) { + SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); + } + } + } + if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { + for (j=0; jregs->TAG_VLAN_MASK_REG; + const bitnum *bit_lookup = (j%2==0)? + state->regs->TAG_VLAN_MASK_EVEN_BIT: + state->regs->TAG_VLAN_MASK_ODD_BIT; + unsigned int vlan_mask; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + addr.m += j/2; + } + vlan_mask = state->vlans[j].ports; + SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); + } + } + + for (i=0; iregs->VLAN_DEFAULT_TAG_REG[i])) { + int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], + state->ports[i].pvid); + if (err < 0) { + return err; + } + } + } + + return ip175c_set_flags(state); +} + +/** + * Uses only the VLAN port mask and the add tag mask to generate the other fields: + * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. + */ +static void ip175c_correct_vlan_state(struct ip17xx_state *state) +{ + int i, j; + state->num_vlans = 0; + for (i=0; ivlans[i].ports != 0) { + state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... + } + } + + for (i=0; iregs->NUM_PORTS; i++) { + unsigned int portmask = (1<vlan_enabled) { + // Share with everybody! + state->ports[i].shareports = (1<regs->NUM_PORTS)-1; + continue; + } + state->ports[i].shareports = portmask; + for (j=0; jvlans[j].ports & portmask) + state->ports[i].shareports |= state->vlans[j].ports; + } + } +} + +static int ip175c_update_state(struct ip17xx_state *state) +{ + ip175c_correct_vlan_state(state); + return ip175c_set_state(state); +} + +static int ip175c_set_vlan_mode(struct ip17xx_state *state) +{ + return ip175c_update_state(state); +} + +static int ip175c_reset(struct ip17xx_state *state) +{ + int err; + + if (REG_SUPP(state->regs->MODE_REG)) { + err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->MODE_REG); + if (err < 0) + return err; + } + + return ip175c_update_state(state); +} + +/*** Low-level functions for IP175D ***/ + +static int ip175d_update_state(struct ip17xx_state *state) +{ + unsigned int filter_mask = 0; + unsigned int ports[16], add[16], rem[16]; + int i, j; + int err = 0; + + for (i = 0; i < 16; i++) { + ports[i] = 0; + add[i] = 0; + rem[i] = 0; + if (!state->vlan_enabled) { + err |= ip_phy_write(state, 22, 14+i, i+1); // default tags + ports[i] = 0x3f; + continue; + } + if (!state->vlans[i].tag) { + // Reset the filter + err |= ip_phy_write(state, 22, 14+i, 0); // tag + continue; + } + filter_mask |= 1 << i; + err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); + ports[i] = state->vlans[i].ports; + for (j = 0; j < 6; j++) { + if (ports[i] & (1 << j)) { + if (state->add_tag & (1 << j)) + add[i] |= 1 << j; + if (state->remove_tag & (1 << j)) + rem[i] |= 1 << j; + } + } + } + + // Port masks, tag adds and removals + for (i = 0; i < 8; i++) { + err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); + } + err |= ip_phy_write(state, 22, 10, filter_mask); + + // Default VLAN tag for each port + for (i = 0; i < 6; i++) + err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); + + return (err ? -EIO : 0); +} + +static int ip175d_set_vlan_mode(struct ip17xx_state *state) +{ + int i; + int err = 0; + + if (state->vlan_enabled) { + // VLAN classification rules: tag-based VLANs, use VID to classify, + // drop packets that cannot be classified. + err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); + + // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, + // VID=0xfff discarded, admin both tagged and untagged, ingress + // filters enabled. + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + + // Egress rules: IGMP processing off, keep VLAN header off + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } else { + // VLAN classification rules: everything off & clear table + err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); + + // Ingress and egress rules: set to defaults + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } + + // Reset default VLAN for each port to 0 + for (i = 0; i < 6; i++) + state->ports[i].pvid = 0; + + err |= ip175d_update_state(state); + + return (err ? -EIO : 0); +} + +static int ip175d_reset(struct ip17xx_state *state) +{ + int err = 0; + + // Disable the special tagging mode + err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); + + // Set 802.1q protocol type + err |= ip_phy_write(state, 22, 3, 0x8100); + + state->vlan_enabled = 0; + err |= ip175d_set_vlan_mode(state); + + return (err ? -EIO : 0); +} + +/*** High-level functions ***/ + +static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + return 0; +} + +static void ip17xx_reset_vlan_config(struct ip17xx_state *state) +{ + int i; + + state->remove_tag = (state->vlan_enabled ? ((1<regs->NUM_PORTS)-1) : 0x0000); + state->add_tag = 0x0000; + for (i = 0; i < MAX_VLANS; i++) { + state->vlans[i].ports = 0x0000; + state->vlans[i].tag = (i ? i : 16); + } + for (i = 0; i < MAX_PORTS; i++) + state->ports[i].pvid = 0; +} + +static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int enable; + + enable = val->value.i; + if (state->vlan_enabled == enable) { + // Do not change any state. + return 0; + } + state->vlan_enabled = enable; + + // Otherwise, if we are switching state, set fields to a known default. + ip17xx_reset_vlan_config(state); + + return state->regs->set_vlan_mode(state); +} + +static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int b; + int ind; + unsigned int ports; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + ports = state->vlans[val->port_vlan].ports; + b = 0; + ind = 0; + while (b < MAX_PORTS) { + if (ports&1) { + int istagged = ((state->add_tag >> b) & 1); + val->value.ports[ind].id = b; + val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); + ind++; + } + b++; + ports >>= 1; + } + val->len = ind; + + return 0; +} + +static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int i; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + state->vlans[val->port_vlan].ports = 0; + for (i = 0; i < val->len; i++) { + unsigned int bitmask = (1<value.ports[i].id); + state->vlans[val->port_vlan].ports |= bitmask; + if (val->value.ports[i].flags & (1<add_tag |= bitmask; + state->remove_tag &= (~bitmask); + } else { + state->add_tag &= (~bitmask); + state->remove_tag |= bitmask; + } + } + + return state->regs->update_state(state); +} + +static int ip17xx_apply(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + + if (REG_SUPP(state->regs->MII_REGISTER_EN)) { + int val = getPhy(state, state->regs->MII_REGISTER_EN); + if (val < 0) { + return val; + } + val |= (1<regs->MII_REGISTER_EN_BIT); + return setPhy(state, state->regs->MII_REGISTER_EN, val); + } + return 0; +} + +static int ip17xx_reset(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + int i, err; + + if (REG_SUPP(state->regs->RESET_REG)) { + err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->RESET_REG); + + /* + * Data sheet specifies reset period to be 2 msec. + * (I don't see any mention of the 2ms delay in the IP178C spec, only + * in IP175C, but it can't hurt.) + */ + mdelay(2); + } + + /* reset switch ports */ + for (i = 0; i < state->regs->NUM_PORTS-1; i++) { + err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + state->router_mode = 0; + state->vlan_enabled = 0; + ip17xx_reset_vlan_config(state); + + return state->regs->reset(state); +} + +static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + if (state->add_tag & (1<port_vlan)) { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 3; // shouldn't ever happen. + else + val->value.i = 1; + } else { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 0; + else + val->value.i = 2; + } + return 0; +} + +static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + state->add_tag &= ~(1<port_vlan); + state->remove_tag &= ~(1<port_vlan); + + if (val->value.i == 0) + state->remove_tag |= (1<port_vlan); + if (val->value.i == 1) + state->add_tag |= (1<port_vlan); + + return state->regs->update_state(state); +} + +/** Get the current phy address */ +static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.p; + return 0; +} + +/** Set a new phy address for low level access to registers */ +static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.p = (u16)-1; + else + state->proc_mii.p = (u16)new_reg; + return 0; +} + +/** Get the current register number */ +static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.m; + return 0; +} + +/** Set a new register address for low level access to registers */ +static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.m = (u16)-1; + else + state->proc_mii.m = (u16)new_reg; + return 0; +} + +/** Get the register content of state->proc_mii */ +static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int retval = -EINVAL; + if (REG_SUPP(state->proc_mii)) + retval = getPhy(state, state->proc_mii); + + if (retval < 0) { + return retval; + } else { + val->value.i = retval; + return 0; + } +} + +/** Write a value to the register defined by phy/reg above */ +static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int myval, err = -EINVAL; + + myval = val->value.i; + if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { + err = setPhy(state, state->proc_mii, (u16)myval); + } + return err; +} + +static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. + return 0; +} + +static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + val->value.i = state->vlans[vlan].tag; + return 0; +} + +static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + int tag = val->value.i; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + if (tag < 0 || tag > 4095) + return -EINVAL; + + state->vlans[vlan].tag = tag; + return state->regs->update_state(state); +} + +static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int ctrl; + int autoneg; + int speed; + if (val->value.i == 100) { + speed = 1; + autoneg = 0; + } else if (val->value.i == 10) { + speed = 0; + autoneg = 0; + } else { + autoneg = 1; + speed = 1; + } + + /* Can't set speed for cpu port */ + if (nr == state->regs->CPU_PORT) + return -EINVAL; + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + if (ctrl < 0) + return -EIO; + + ctrl &= (~(1<<12)); + ctrl &= (~(1<<13)); + ctrl |= (autoneg<<12); + ctrl |= (speed<<13); + + return ip_phy_write(state, nr, 0, ctrl); +} + +static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int speed, status; + + if (nr == state->regs->CPU_PORT) { + val->value.i = 100; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (status < 0 || speed < 0) + return -EIO; + + if (status & 4) + val->value.i = ((speed & (1<<11)) ? 100 : 10); + else + val->value.i = 0; + + return 0; +} + +static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int ctrl, speed, status; + int nr = val->port_vlan; + int len; + char *buf = state->buf; // fixed-length at 80. + + if (nr == state->regs->CPU_PORT) { + sprintf(buf, "up, 100 Mbps, cpu port"); + val->value.s = buf; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (ctrl < 0 || status < 0 || speed < 0) + return -EIO; + + if (status & 4) + len = sprintf(buf, "up, %d Mbps, %s duplex", + ((speed & (1<<11)) ? 100 : 10), + ((speed & (1<<10)) ? "full" : "half")); + else + len = sprintf(buf, "down"); + + if (ctrl & (1<<12)) { + len += sprintf(buf+len, ", auto-negotiate"); + if (!(status & (1<<5))) + len += sprintf(buf+len, " (in progress)"); + } else { + len += sprintf(buf+len, ", fixed speed (%d)", + ((ctrl & (1<<13)) ? 100 : 10)); + } + + buf[len] = '\0'; + val->value.s = buf; + return 0; +} + +static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct ip17xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + return 0; +} + +static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct ip17xx_state *state = get_state(dev); + + if (val < 0 || val >= MAX_VLANS) + return -EINVAL; + + state->ports[port].pvid = val; + return state->regs->update_state(state); +} + + +enum Ports { + IP17XX_PORT_STATUS, + IP17XX_PORT_LINK, + IP17XX_PORT_TAGGED, + IP17XX_PORT_PVID, +}; + +enum Globals { + IP17XX_ENABLE_VLAN, + IP17XX_GET_NAME, + IP17XX_REGISTER_PHY, + IP17XX_REGISTER_MII, + IP17XX_REGISTER_VALUE, + IP17XX_REGISTER_ERRNO, +}; + +enum Vlans { + IP17XX_VLAN_TAG, +}; + +static const struct switch_attr ip17xx_global[] = { + [IP17XX_ENABLE_VLAN] = { + .id = IP17XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Flag to enable or disable VLANs and tagging", + .get = ip17xx_get_enable_vlan, + .set = ip17xx_set_enable_vlan, + }, + [IP17XX_GET_NAME] = { + .id = IP17XX_GET_NAME, + .type = SWITCH_TYPE_STRING, + .description = "Returns the type of IC+ chip.", + .name = "name", + .get = ip17xx_read_name, + .set = NULL, + }, + /* jal: added for low level debugging etc. */ + [IP17XX_REGISTER_PHY] = { + .id = IP17XX_REGISTER_PHY, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set PHY (0-4, or 29,30,31)", + .name = "phy", + .get = ip17xx_get_phy, + .set = ip17xx_set_phy, + }, + [IP17XX_REGISTER_MII] = { + .id = IP17XX_REGISTER_MII, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set MII register number (0-31)", + .name = "reg", + .get = ip17xx_get_reg, + .set = ip17xx_set_reg, + }, + [IP17XX_REGISTER_VALUE] = { + .id = IP17XX_REGISTER_VALUE, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: read/write to register (0-65535)", + .name = "val", + .get = ip17xx_get_val, + .set = ip17xx_set_val, + }, +}; + +static const struct switch_attr ip17xx_vlan[] = { + [IP17XX_VLAN_TAG] = { + .id = IP17XX_VLAN_TAG, + .type = SWITCH_TYPE_INT, + .description = "VLAN ID (0-4095) [IP175D only]", + .name = "vid", + .get = ip17xx_get_tag, + .set = ip17xx_set_tag, + } +}; + +static const struct switch_attr ip17xx_port[] = { + [IP17XX_PORT_STATUS] = { + .id = IP17XX_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Returns Detailed port status", + .name = "status", + .get = ip17xx_get_port_status, + .set = NULL, + }, + [IP17XX_PORT_LINK] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", + .name = "link", + .get = ip17xx_get_port_speed, + .set = ip17xx_set_port_speed, + }, + [IP17XX_PORT_TAGGED] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", + .name = "tagged", + .get = ip17xx_get_tagged, + .set = ip17xx_set_tagged, + }, +}; + +static const struct switch_dev_ops ip17xx_ops = { + .attr_global = { + .attr = ip17xx_global, + .n_attr = ARRAY_SIZE(ip17xx_global), + }, + .attr_port = { + .attr = ip17xx_port, + .n_attr = ARRAY_SIZE(ip17xx_port), + }, + .attr_vlan = { + .attr = ip17xx_vlan, + .n_attr = ARRAY_SIZE(ip17xx_vlan), + }, + + .get_port_pvid = ip17xx_get_pvid, + .set_port_pvid = ip17xx_set_pvid, + .get_vlan_ports = ip17xx_get_ports, + .set_vlan_ports = ip17xx_set_ports, + .apply_config = ip17xx_apply, + .reset_switch = ip17xx_reset, +}; + +static int ip17xx_probe(struct phy_device *pdev) +{ + struct ip17xx_state *state; + struct switch_dev *dev; + int err; + + /* We only attach to PHY 0, but use all available PHYs */ + if (pdev->mdio.addr != 0) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + dev = &state->dev; + + pdev->priv = state; + state->mii_bus = pdev->mdio.bus; + + err = get_model(state); + if (err < 0) + goto error; + + dev->vlans = MAX_VLANS; + dev->cpu_port = state->regs->CPU_PORT; + dev->ports = state->regs->NUM_PORTS; + dev->name = state->regs->NAME; + dev->ops = &ip17xx_ops; + + pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev)); + return 0; + +error: + kfree(state); + return err; +} + +static int ip17xx_config_init(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + struct net_device *dev = pdev->attached_dev; + int err; + + err = register_switch(&state->dev, dev); + if (err < 0) + return err; + + state->registered = true; + ip17xx_reset(&state->dev); + return 0; +} + +static void ip17xx_remove(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + + if (state->registered) + unregister_switch(&state->dev); + kfree(state); +} + +static int ip17xx_config_aneg(struct phy_device *pdev) +{ + return 0; +} + +static int ip17xx_aneg_done(struct phy_device *pdev) +{ + return 1; /* Return any positive value */ +} + +static int ip17xx_update_link(struct phy_device *pdev) +{ + pdev->link = 1; + return 0; +} + +static int ip17xx_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->pause = pdev->asym_pause = 0; + pdev->link = 1; + + return 0; +} + +static struct phy_driver ip17xx_driver[] = { + { + .name = "IC+ IP17xx", + .phy_id = 0x02430c00, + .phy_id_mask = 0x0ffffc00, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + } +}; + +module_phy_driver(ip17xx_driver); + +MODULE_AUTHOR("Patrick Horn "); +MODULE_AUTHOR("Felix Fietkau "); +MODULE_AUTHOR("Martin Mares "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.c b/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.c new file mode 100644 index 0000000000..9a689e6c81 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.c @@ -0,0 +1,947 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mvsw61xx.h" + +MODULE_DESCRIPTION("Marvell 88E61xx Switch driver"); +MODULE_AUTHOR("Claudio Leite "); +MODULE_AUTHOR("Nikita Nazarenko "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mvsw61xx"); + +/* + * Register access is done through direct or indirect addressing, + * depending on how the switch is physically connected. + * + * Direct addressing: all port and global registers directly + * accessible via an address/register pair + * + * Indirect addressing: switch is mapped at a single address, + * port and global registers accessible via a single command/data + * register pair + */ + +static int +mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = bus->read(bus, addr, reg); + if ((r & mask) == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static u16 +r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) +{ + u16 ind_addr; + + if (!indirect) + return bus->read(bus, addr, reg); + + /* Indirect read: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address and request read */ + ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); + + /* Wait until it's ready */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Read the requested data */ + return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); +} + +static void +w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, + int reg, u16 val) +{ + u16 ind_addr; + + if (!indirect) { + bus->write(bus, addr, reg, val); + return; + } + + /* Indirect write: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load the data to be written */ + bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); + + /* Wait again for switch to be free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address, and issue write command */ + ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); +} + +/* swconfig support */ + +static inline u16 +sr16(struct switch_dev *dev, int addr, int reg) +{ + struct mvsw61xx_state *state = get_state(dev); + + return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); +} + +static inline void +sw16(struct switch_dev *dev, int addr, int reg, u16 val) +{ + struct mvsw61xx_state *state = get_state(dev); + + w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); +} + +static int +mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = sr16(dev, addr, reg) & mask; + if (r == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static int +mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg) +{ + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg); + + if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0) + return -ETIMEDOUT; + + return sr16(dev, MV_GLOBAL2REG(SMI_DATA)); +} + +static int +mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val) +{ + sw16(dev, MV_GLOBAL2REG(SMI_DATA), val); + + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg); + + return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0; +} + +static int +mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg) +{ + int ret; + + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + ret = mvsw61xx_mdio_read(dev, port, reg); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); + + return ret; +} + +static void +mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg, + u16 val) +{ + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + mvsw61xx_mdio_write(dev, port, reg, val); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); +} + +static int +mvsw61xx_get_port_mask(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + char *buf = state->buf; + int port, len, i; + u16 reg; + + port = val->port_vlan; + reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; + + len = sprintf(buf, "0x%04x: ", reg); + + for (i = 0; i < MV_PORTS; i++) { + if (reg & (1 << i)) + len += sprintf(buf + len, "%d ", i); + else if (i == port) + len += sprintf(buf + len, "(%d) ", i); + } + + val->value.s = buf; + + return 0; +} + +static int +mvsw61xx_get_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->ports[val->port_vlan].qmode; + + return 0; +} + +static int +mvsw61xx_set_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->ports[val->port_vlan].qmode = val->value.i; + + return 0; +} + +static int +mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + + return 0; +} + +static int +mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct mvsw61xx_state *state = get_state(dev); + + if (val < 0 || val >= MV_VLANS) + return -EINVAL; + + state->ports[port].pvid = (u16)val; + + return 0; +} + +static int +mvsw61xx_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + u16 status, speed; + + status = sr16(dev, MV_PORTREG(STATUS, port)); + + link->link = status & MV_PORT_STATUS_LINK; + if (!link->link) + return 0; + + link->duplex = status & MV_PORT_STATUS_FDX; + + speed = (status & MV_PORT_STATUS_SPEED_MASK) >> + MV_PORT_STATUS_SPEED_SHIFT; + + switch (speed) { + case MV_PORT_STATUS_SPEED_10: + link->speed = SWITCH_PORT_SPEED_10; + break; + case MV_PORT_STATUS_SPEED_100: + link->speed = SWITCH_PORT_SPEED_100; + break; + case MV_PORT_STATUS_SPEED_1000: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + + return 0; +} + +static int mvsw61xx_get_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, j, mode, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + for (i = 0, j = 0; i < dev->ports; i++) { + if (state->vlans[vno].mask & (1 << i)) { + val->value.ports[j].id = i; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + if (mode == MV_VTUCTL_EGRESS_TAGGED) + val->value.ports[j].flags = + (1 << SWITCH_PORT_FLAG_TAGGED); + else + val->value.ports[j].flags = 0; + + j++; + } + } + + val->len = j; + + return 0; +} + +static int mvsw61xx_set_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode, pno, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].mask = 0; + state->vlans[vno].port_mode = 0; + state->vlans[vno].port_sstate = 0; + + if(state->vlans[vno].vid == 0) + state->vlans[vno].vid = vno; + + for (i = 0; i < val->len; i++) { + pno = val->value.ports[i].id; + + state->vlans[vno].mask |= (1 << pno); + if (val->value.ports[i].flags & + (1 << SWITCH_PORT_FLAG_TAGGED)) + mode = MV_VTUCTL_EGRESS_TAGGED; + else + mode = MV_VTUCTL_EGRESS_UNTAGGED; + + state->vlans[vno].port_mode |= mode << (pno * 4); + state->vlans[vno].port_sstate |= + MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2); + } + + /* + * DISCARD is nonzero, so it must be explicitly + * set on ports not in the VLAN. + */ + for (i = 0; i < dev->ports; i++) + if (!(state->vlans[vno].mask & (1 << i))) + state->vlans[vno].port_mode |= + MV_VTUCTL_DISCARD << (i * 4); + + return 0; +} + +static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (state->vlans[vno].port_based) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (val->value.i == 1) + state->vlans[vno].port_based = true; + else + state->vlans[vno].port_based = false; + + return 0; +} + +static int mvsw61xx_get_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + val->value.i = state->vlans[vno].vid; + + return 0; +} + +static int mvsw61xx_set_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].vid = val->value.i; + + return 0; +} + +static int mvsw61xx_get_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + + return 0; +} + +static int mvsw61xx_set_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->vlan_enabled = val->value.i; + + return 0; +} + +static int mvsw61xx_vtu_program(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + u16 v1, v2, s1, s2; + int i; + + /* Flush */ + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE); + + /* Write VLAN table */ + for (i = 1; i < dev->vlans; i++) { + if (state->vlans[i].mask == 0 || + state->vlans[i].vid == 0 || + state->vlans[i].port_based == true) + continue; + + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write per-VLAN port state into STU */ + s1 = (u16) (state->vlans[i].port_sstate & 0xffff); + s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), s1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), s2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write VLAN information into VTU */ + v1 = (u16) (state->vlans[i].port_mode & 0xffff); + v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), + MV_VTU_VID_VALID | state->vlans[i].vid); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_FID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + } + + return 0; +} + +static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode; + + for (i = 0; i < dev->ports; i++) { + if (!(state->vlans[vno].mask & (1 << i))) + continue; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + + if(mode != MV_VTUCTL_EGRESS_TAGGED) + state->ports[i].pvid = state->vlans[vno].vid; + + if (state->vlans[vno].port_based) { + state->ports[i].mask |= state->vlans[vno].mask; + state->ports[i].fdb = vno; + } + else + state->ports[i].qmode = MV_8021Q_MODE_SECURE; + } +} + +static int mvsw61xx_update_state(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + if (!state->registered) + return -EINVAL; + + /* + * Set 802.1q-only mode if vlan_enabled is true. + * + * Without this, even if 802.1q is enabled for + * a port/VLAN, it still depends on the port-based + * VLAN mask being set. + * + * With this setting, port-based VLANs are still + * functional, provided the VID is not in the VTU. + */ + reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); + + if (state->vlan_enabled) + reg |= MV_8021Q_VLAN_ONLY; + else + reg &= ~MV_8021Q_VLAN_ONLY; + + sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); + + /* + * Set port-based VLAN masks on each port + * based only on VLAN definitions known to + * the driver (i.e. in state). + * + * This means any pre-existing port mapping is + * wiped out once our driver is initialized. + */ + for (i = 0; i < dev->ports; i++) { + state->ports[i].mask = 0; + state->ports[i].qmode = MV_8021Q_MODE_DISABLE; + } + + for (i = 0; i < dev->vlans; i++) + mvsw61xx_vlan_port_config(dev, i); + + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; + reg |= state->ports[i].pvid; + sw16(dev, MV_PORTREG(VLANID, i), reg); + + state->ports[i].mask &= ~(1 << i); + + /* set default forwarding DB number and port mask */ + reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK; + reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) & + MV_FDB_HI_MASK; + sw16(dev, MV_PORTREG(CONTROL1, i), reg); + + reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) | + state->ports[i].mask; + sw16(dev, MV_PORTREG(VLANMAP, i), reg); + + reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & + ~MV_8021Q_MODE_MASK; + reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; + sw16(dev, MV_PORTREG(CONTROL2, i), reg); + } + + mvsw61xx_vtu_program(dev); + + return 0; +} + +static int mvsw61xx_apply(struct switch_dev *dev) +{ + return mvsw61xx_update_state(dev); +} + +static void mvsw61xx_enable_serdes(struct switch_dev *dev) +{ + int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR); + if (bmcr < 0) + return; + + if (bmcr & BMCR_PDOWN) + mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR, + bmcr & ~BMCR_PDOWN); +} + +static int _mvsw61xx_reset(struct switch_dev *dev, bool full) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + /* Disable all ports before reset */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) & + ~MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; + + sw16(dev, MV_GLOBALREG(CONTROL), reg); + if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL), + MV_CONTROL_RESET, 0) < 0) + return -ETIMEDOUT; + + for (i = 0; i < dev->ports; i++) { + state->ports[i].fdb = 0; + state->ports[i].qmode = 0; + state->ports[i].mask = 0; + state->ports[i].pvid = 0; + + /* Force flow control off */ + reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK; + reg |= MV_PHYCTL_FC_DISABLE; + sw16(dev, MV_PORTREG(PHYCTL, i), reg); + + /* Set port association vector */ + sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); + + /* power up phys */ + if (full && i < 5) { + mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL, + MV_SPEC_MDI_CROSS_AUTO | + MV_SPEC_ENERGY_DETECT | + MV_SPEC_DOWNSHIFT_COUNTER); + mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET | + BMCR_ANENABLE | BMCR_FULLDPLX | + BMCR_SPEED1000); + } + + /* enable SerDes if necessary */ + if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) { + u16 sts = sr16(dev, MV_PORTREG(STATUS, i)); + u16 mode = sts & MV_PORT_STATUS_CMODE_MASK; + + if (mode == MV_PORT_STATUS_CMODE_100BASE_X || + mode == MV_PORT_STATUS_CMODE_1000BASE_X || + mode == MV_PORT_STATUS_CMODE_SGMII) { + mvsw61xx_enable_serdes(dev); + } + } + } + + for (i = 0; i < dev->vlans; i++) { + state->vlans[i].port_based = false; + state->vlans[i].mask = 0; + state->vlans[i].vid = 0; + state->vlans[i].port_mode = 0; + state->vlans[i].port_sstate = 0; + } + + state->vlan_enabled = 0; + + mvsw61xx_update_state(dev); + + /* Re-enable ports */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) | + MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + return 0; +} + +static int mvsw61xx_reset(struct switch_dev *dev) +{ + return _mvsw61xx_reset(dev, false); +} + +enum { + MVSW61XX_ENABLE_VLAN, +}; + +enum { + MVSW61XX_VLAN_PORT_BASED, + MVSW61XX_VLAN_ID, +}; + +enum { + MVSW61XX_PORT_MASK, + MVSW61XX_PORT_QMODE, +}; + +static const struct switch_attr mvsw61xx_global[] = { + [MVSW61XX_ENABLE_VLAN] = { + .id = MVSW61XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable 802.1q VLAN support", + .get = mvsw61xx_get_enable_vlan, + .set = mvsw61xx_set_enable_vlan, + }, +}; + +static const struct switch_attr mvsw61xx_vlan[] = { + [MVSW61XX_VLAN_PORT_BASED] = { + .id = MVSW61XX_VLAN_PORT_BASED, + .type = SWITCH_TYPE_INT, + .name = "port_based", + .description = "Use port-based (non-802.1q) VLAN only", + .get = mvsw61xx_get_vlan_port_based, + .set = mvsw61xx_set_vlan_port_based, + }, + [MVSW61XX_VLAN_ID] = { + .id = MVSW61XX_VLAN_ID, + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "Get/set VLAN ID", + .get = mvsw61xx_get_vid, + .set = mvsw61xx_set_vid, + }, +}; + +static const struct switch_attr mvsw61xx_port[] = { + [MVSW61XX_PORT_MASK] = { + .id = MVSW61XX_PORT_MASK, + .type = SWITCH_TYPE_STRING, + .description = "Port-based VLAN mask", + .name = "mask", + .get = mvsw61xx_get_port_mask, + .set = NULL, + }, + [MVSW61XX_PORT_QMODE] = { + .id = MVSW61XX_PORT_QMODE, + .type = SWITCH_TYPE_INT, + .description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", + .name = "qmode", + .get = mvsw61xx_get_port_qmode, + .set = mvsw61xx_set_port_qmode, + }, +}; + +static const struct switch_dev_ops mvsw61xx_ops = { + .attr_global = { + .attr = mvsw61xx_global, + .n_attr = ARRAY_SIZE(mvsw61xx_global), + }, + .attr_vlan = { + .attr = mvsw61xx_vlan, + .n_attr = ARRAY_SIZE(mvsw61xx_vlan), + }, + .attr_port = { + .attr = mvsw61xx_port, + .n_attr = ARRAY_SIZE(mvsw61xx_port), + }, + .get_port_link = mvsw61xx_get_port_link, + .get_port_pvid = mvsw61xx_get_port_pvid, + .set_port_pvid = mvsw61xx_set_port_pvid, + .get_vlan_ports = mvsw61xx_get_vlan_ports, + .set_vlan_ports = mvsw61xx_set_vlan_ports, + .apply_config = mvsw61xx_apply, + .reset_switch = mvsw61xx_reset, +}; + +/* end swconfig stuff */ + +static int mvsw61xx_probe(struct platform_device *pdev) +{ + struct mvsw61xx_state *state; + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio; + char *model_str; + u32 val; + int err; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mdio = of_parse_phandle(np, "mii-bus", 0); + if (!mdio) { + dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); + err = -ENODEV; + goto out_err; + } + + state->bus = of_mdio_find_bus(mdio); + if (!state->bus) { + dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); + err = -ENODEV; + goto out_err; + } + + state->is_indirect = of_property_read_bool(np, "is-indirect"); + + if (state->is_indirect) { + if (of_property_read_u32(np, "reg", &val)) { + dev_err(&pdev->dev, "Switch address not specified\n"); + err = -ENODEV; + goto out_err; + } + + state->base_addr = val; + } else { + state->base_addr = MV_BASE; + } + + state->model = r16(state->bus, state->is_indirect, state->base_addr, + MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + + switch(state->model) { + case MV_IDENT_VALUE_6171: + model_str = MV_IDENT_STR_6171; + break; + case MV_IDENT_VALUE_6172: + model_str = MV_IDENT_STR_6172; + break; + case MV_IDENT_VALUE_6176: + model_str = MV_IDENT_STR_6176; + break; + case MV_IDENT_VALUE_6352: + model_str = MV_IDENT_STR_6352; + break; + default: + dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n", + state->base_addr); + err = -ENODEV; + goto out_err; + } + + platform_set_drvdata(pdev, state); + dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str, + state->bus->id, state->base_addr); + + dev_info(&pdev->dev, "Using %sdirect addressing\n", + (state->is_indirect ? "in" : "")); + + if (of_property_read_u32(np, "cpu-port-0", &val)) { + dev_err(&pdev->dev, "CPU port not set\n"); + err = -ENODEV; + goto out_err; + } + + state->cpu_port0 = val; + + if (!of_property_read_u32(np, "cpu-port-1", &val)) + state->cpu_port1 = val; + else + state->cpu_port1 = -1; + + state->dev.vlans = MV_VLANS; + state->dev.cpu_port = state->cpu_port0; + state->dev.ports = MV_PORTS; + state->dev.name = model_str; + state->dev.ops = &mvsw61xx_ops; + state->dev.alias = dev_name(&pdev->dev); + + _mvsw61xx_reset(&state->dev, true); + + err = register_switch(&state->dev, NULL); + if (err < 0) + goto out_err; + + state->registered = true; + + return 0; +out_err: + kfree(state); + return err; +} + +static int +mvsw61xx_remove(struct platform_device *pdev) +{ + struct mvsw61xx_state *state = platform_get_drvdata(pdev); + + if (state->registered) + unregister_switch(&state->dev); + + kfree(state); + + return 0; +} + +static const struct of_device_id mvsw61xx_match[] = { + { .compatible = "marvell,88e6171" }, + { .compatible = "marvell,88e6172" }, + { .compatible = "marvell,88e6176" }, + { .compatible = "marvell,88e6352" }, + { } +}; +MODULE_DEVICE_TABLE(of, mvsw61xx_match); + +static struct platform_driver mvsw61xx_driver = { + .probe = mvsw61xx_probe, + .remove = mvsw61xx_remove, + .driver = { + .name = "mvsw61xx", + .of_match_table = of_match_ptr(mvsw61xx_match), + .owner = THIS_MODULE, + }, +}; + +static int __init mvsw61xx_module_init(void) +{ + return platform_driver_register(&mvsw61xx_driver); +} +late_initcall(mvsw61xx_module_init); + +static void __exit mvsw61xx_module_exit(void) +{ + platform_driver_unregister(&mvsw61xx_driver); +} +module_exit(mvsw61xx_module_exit); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.h b/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.h new file mode 100644 index 0000000000..a07b09cd14 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/mvsw61xx.h @@ -0,0 +1,292 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __MVSW61XX_H +#define __MVSW61XX_H + +#define MV_PORTS 7 +#define MV_PORTS_MASK ((1 << MV_PORTS) - 1) + +#define MV_BASE 0x10 + +#define MV_SWITCHPORT_BASE 0x10 +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xb) + +#define MV_VLANS 64 + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_PHYCTL = 0x01, + MV_PORT_JAMCTL = 0x02, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_CONTROL1 = 0x05, + MV_PORT_VLANMAP = 0x06, + MV_PORT_VLANID = 0x07, + MV_PORT_CONTROL2 = 0x08, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RX_DISCARD_LOW = 0x10, + MV_PORT_RX_DISCARD_HIGH = 0x11, + MV_PORT_IN_FILTERED = 0x12, + MV_PORT_OUT_ACCEPTED = 0x13, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORT_STATUS_FDX = (1 << 10), + MV_PORT_STATUS_LINK = (1 << 11), +}; + +enum { + MV_PORT_STATUS_CMODE_100BASE_X = 0x8, + MV_PORT_STATUS_CMODE_1000BASE_X = 0x9, + MV_PORT_STATUS_CMODE_SGMII = 0xa, +}; + +#define MV_PORT_STATUS_CMODE_MASK 0xf + +enum { + MV_PORT_STATUS_SPEED_10 = 0x00, + MV_PORT_STATUS_SPEED_100 = 0x01, + MV_PORT_STATUS_SPEED_1000 = 0x02, +}; +#define MV_PORT_STATUS_SPEED_SHIFT 8 +#define MV_PORT_STATUS_SPEED_MASK (3 << 8) + +enum { + MV_PORTCTRL_DISABLED = (0 << 0), + MV_PORTCTRL_BLOCKING = (1 << 0), + MV_PORTCTRL_LEARNING = (2 << 0), + MV_PORTCTRL_FORWARDING = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), + MV_PORTCTRL_EGRESS = (1 << 12), +}; + +#define MV_PHYCTL_FC_MASK (3 << 6) + +enum { + MV_PHYCTL_FC_ENABLE = (3 << 6), + MV_PHYCTL_FC_DISABLE = (1 << 6), +}; + +enum { + MV_8021Q_EGRESS_UNMODIFIED = 0x00, + MV_8021Q_EGRESS_UNTAGGED = 0x01, + MV_8021Q_EGRESS_TAGGED = 0x02, + MV_8021Q_EGRESS_ADDTAG = 0x03, +}; + +#define MV_8021Q_MODE_SHIFT 10 +#define MV_8021Q_MODE_MASK (0x3 << MV_8021Q_MODE_SHIFT) + +enum { + MV_8021Q_MODE_DISABLE = 0x00, + MV_8021Q_MODE_FALLBACK = 0x01, + MV_8021Q_MODE_CHECK = 0x02, + MV_8021Q_MODE_SECURE = 0x03, +}; + +enum { + MV_8021Q_VLAN_ONLY = (1 << 15), +}; + +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_ATU_FID0 = 0x01, + MV_SWITCH_ATU_FID1 = 0x02, + MV_SWITCH_ATU_SID = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, + MV_SWITCH_GLOBAL = 0x1b, + MV_SWITCH_GLOBAL2 = 0x1c, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), + MV_SWITCHCTL_PHYIE = (1 << 1), + MV_SWITCHCTL_ATUDONE = (1 << 2), + MV_SWITCHCTL_ATUIE = (1 << 3), + MV_SWITCHCTL_CTRMODE = (1 << 8), + MV_SWITCHCTL_RELOAD = (1 << 9), + MV_SWITCHCTL_MSIZE = (1 << 10), + MV_SWITCHCTL_DROP = (1 << 13), +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU = (6 << 12), + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +enum { + MV_GLOBAL_STATUS = 0x00, + MV_GLOBAL_ATU_FID = 0x01, + MV_GLOBAL_VTU_FID = 0x02, + MV_GLOBAL_VTU_SID = 0x03, + MV_GLOBAL_CONTROL = 0x04, + MV_GLOBAL_VTU_OP = 0x05, + MV_GLOBAL_VTU_VID = 0x06, + MV_GLOBAL_VTU_DATA1 = 0x07, + MV_GLOBAL_VTU_DATA2 = 0x08, + MV_GLOBAL_VTU_DATA3 = 0x09, + MV_GLOBAL_CONTROL2 = 0x1c, +}; +#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type + +enum { + MV_GLOBAL2_SMI_OP = 0x18, + MV_GLOBAL2_SMI_DATA = 0x19, + MV_GLOBAL2_SDET_POLARITY = 0x1d, +}; +#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type + +enum { + MV_VTU_VID_VALID = (1 << 12), +}; + +enum { + MV_VTUOP_PURGE = (1 << 12), + MV_VTUOP_LOAD = (3 << 12), + MV_VTUOP_INPROGRESS = (1 << 15), + MV_VTUOP_STULOAD = (5 << 12), + MV_VTUOP_VTU_GET_NEXT = (4 << 12), + MV_VTUOP_STU_GET_NEXT = (6 << 12), + MV_VTUOP_GET_VIOLATION = (7 << 12), +}; + +enum { + MV_CONTROL_RESET = (1 << 15), + MV_CONTROL_PPU_ENABLE = (1 << 14), +}; + +enum { + MV_VTUCTL_EGRESS_UNMODIFIED = (0 << 0), + MV_VTUCTL_EGRESS_UNTAGGED = (1 << 0), + MV_VTUCTL_EGRESS_TAGGED = (2 << 0), + MV_VTUCTL_DISCARD = (3 << 0), +}; + +enum { + MV_STUCTL_STATE_DISABLED = (0 << 0), + MV_STUCTL_STATE_BLOCKING = (1 << 0), + MV_STUCTL_STATE_LEARNING = (2 << 0), + MV_STUCTL_STATE_FORWARDING = (3 << 0), +}; + +enum { + MV_INDIRECT_REG_CMD = 0, + MV_INDIRECT_REG_DATA = 1, +}; + +enum { + MV_INDIRECT_INPROGRESS = 0x8000, + MV_INDIRECT_WRITE = 0x9400, + MV_INDIRECT_READ = 0x9800, +}; +#define MV_INDIRECT_ADDR_S 5 + +#define MV_IDENT_MASK 0xfff0 + +#define MV_IDENT_VALUE_6171 0x1710 +#define MV_IDENT_STR_6171 "MV88E6171" + +#define MV_IDENT_VALUE_6172 0x1720 +#define MV_IDENT_STR_6172 "MV88E6172" + +#define MV_IDENT_VALUE_6176 0x1760 +#define MV_IDENT_STR_6176 "MV88E6176" + +#define MV_IDENT_VALUE_6352 0x3520 +#define MV_IDENT_STR_6352 "MV88E6352" + +#define MV_PVID_MASK 0x0fff + +#define MV_FDB_HI_MASK 0x00ff +#define MV_FDB_LO_MASK 0xf000 +#define MV_FDB_HI_SHIFT 4 +#define MV_FDB_LO_SHIFT 12 + +/* Marvell Specific PHY register */ +#define MII_MV_SPEC_CTRL 16 +enum { + MV_SPEC_MDI_CROSS_AUTO = (0x6 << 4), + MV_SPEC_ENERGY_DETECT = (0x3 << 8), + MV_SPEC_DOWNSHIFT_COUNTER = (0x3 << 12), +}; + +#define MII_MV_PAGE 22 + +#define MV_REG_FIBER_SERDES 0xf +#define MV_PAGE_FIBER_SERDES 0x1 + +struct mvsw61xx_state { + struct switch_dev dev; + struct mii_bus *bus; + int base_addr; + u16 model; + + bool registered; + bool is_indirect; + + int cpu_port0; + int cpu_port1; + + int vlan_enabled; + struct port_state { + u16 fdb; + u16 pvid; + u16 mask; + u8 qmode; + } ports[MV_PORTS]; + + struct vlan_state { + bool port_based; + + u16 mask; + u16 vid; + u32 port_mode; + u32 port_sstate; + } vlans[MV_VLANS]; + + char buf[128]; +}; + +#define get_state(_dev) container_of((_dev), struct mvsw61xx_state, dev) + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.c b/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.c new file mode 100644 index 0000000000..043978feb9 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.c @@ -0,0 +1,444 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "mvswitch.h" + +/* Undefine this to use trailer mode instead. + * I don't know if header mode works with all chips */ +#define HEADER_MODE 1 + +MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +#define MVSWITCH_MAGIC 0x88E6060 + +struct mvswitch_priv { + netdev_features_t orig_features; + u8 vlans[16]; +}; + +#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *phydev, int addr, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, addr, reg); +} + +static inline void +w16(struct phy_device *phydev, int addr, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, addr, reg, val); +} + + +static struct sk_buff * +mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + char *buf = NULL; + u16 vid; + + priv = dev->phy_ptr; + if (unlikely(!priv)) + goto error; + + if (unlikely(skb->len < 16)) + goto error; + +#ifdef HEADER_MODE + if (__vlan_hwaccel_get_tag(skb, &vid)) + goto error; + + if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { + if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) + goto error_expand; + if (skb->len < 62) + skb->len = 62; + } + buf = skb_push(skb, MV_HEADER_SIZE); +#else + if (__vlan_get_tag(skb, &vid)) + goto error; + + if (unlikely((vid > 15 || !priv->vlans[vid]))) + goto error; + + if (skb->len <= 64) { + if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) + goto error_expand; + + buf = skb->data + 64; + skb->len = 64 + MV_TRAILER_SIZE; + } else { + if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { + if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) + goto error_expand; + } + buf = skb_put(skb, 4); + } + + /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ + memmove(skb->data + 4, skb->data, 12); + skb->data += 4; + skb->len -= 4; + skb->mac_header += 4; +#endif + + if (!buf) + goto error; + + +#ifdef HEADER_MODE + /* prepend the tag */ + *((__be16 *) buf) = cpu_to_be16( + ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | + ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) + ); +#else + /* append the tag */ + *((__be32 *) buf) = cpu_to_be32(( + (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | + ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) + )); +#endif + + return skb; + +error_expand: + if (net_ratelimit()) + printk("%s: failed to expand/update skb for the switch\n", dev->name); + +error: + /* any errors? drop the packet! */ + dev_kfree_skb_any(skb); + return NULL; +} + +static void +mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + unsigned char *buf; + int vlan = -1; + int i; + + priv = dev->phy_ptr; + if (WARN_ON_ONCE(!priv)) + return; + +#ifdef HEADER_MODE + buf = skb->data; + skb_pull(skb, MV_HEADER_SIZE); +#else + buf = skb->data + skb->len - MV_TRAILER_SIZE; + if (buf[0] != 0x80) + return; +#endif + + /* look for the vlan matching the incoming port */ + for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { + if ((1 << buf[1]) & priv->vlans[i]) + vlan = i; + } + + if (vlan == -1) + return; + + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); +} + + +static int +mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = r16(pdev, addr, reg) & mask; + if (r == val) + return 0; + } while(--i > 0); + return -ETIMEDOUT; +} + +static int +mvswitch_config_init(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + u8 vlmap = 0; + int i; + + if (!dev) + return -EINVAL; + + printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + dev->phy_ptr = priv; + pdev->irq = PHY_POLL; +#ifdef HEADER_MODE + dev->flags |= IFF_PROMISC; +#endif + + /* initialize default vlans */ + for (i = 0; i < MV_PORTS; i++) + priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); + + /* before entering reset, disable all ports */ + for (i = 0; i < MV_PORTS; i++) + w16(pdev, MV_PORTREG(CONTROL, i), 0x00); + + msleep(2); /* wait for the status change to settle in */ + + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); + + i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); + if (i < 0) { + printk("%s: Timeout waiting for the switch to reset.\n", dev->name); + return i; + } + + /* set the ATU flags */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), + MV_ATUCTL_NO_LEARN | + MV_ATUCTL_ATU_1K | + MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ + ); + + /* initialize the cpu port */ + w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), +#ifdef HEADER_MODE + MV_PORTCTRL_HEADER | +#else + MV_PORTCTRL_RXTR | + MV_PORTCTRL_TXTR | +#endif + MV_PORTCTRL_ENABLED + ); + /* wait for the phy change to settle in */ + msleep(2); + for (i = 0; i < MV_PORTS; i++) { + u8 pvid = 0; + int j; + + vlmap = 0; + + /* look for the matching vlan */ + for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { + if (priv->vlans[j] & (1 << i)) { + vlmap = priv->vlans[j]; + pvid = j; + } + } + /* leave port unconfigured if it's not part of a vlan */ + if (!vlmap) + continue; + + /* add the cpu port to the allowed destinations list */ + vlmap |= (1 << MV_CPUPORT); + + /* take port out of its own vlan destination map */ + vlmap &= ~(1 << i); + + /* apply vlan settings */ + w16(pdev, MV_PORTREG(VLANMAP, i), + MV_PORTVLAN_PORTS(vlmap) | + MV_PORTVLAN_ID(i) + ); + + /* re-enable port */ + w16(pdev, MV_PORTREG(CONTROL, i), + MV_PORTCTRL_ENABLED + ); + } + + w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), + MV_PORTVLAN_ID(MV_CPUPORT) + ); + + /* set the port association vector */ + for (i = 0; i <= MV_PORTS; i++) { + w16(pdev, MV_PORTREG(ASSOC, i), + MV_PORTASSOC_PORTS(1 << i) + ); + } + + /* init switch control */ + w16(pdev, MV_SWITCHREG(CTRL), + MV_SWITCHCTL_MSIZE | + MV_SWITCHCTL_DROP + ); + + dev->eth_mangle_rx = mvswitch_mangle_rx; + dev->eth_mangle_tx = mvswitch_mangle_tx; + priv->orig_features = dev->features; + +#ifdef HEADER_MODE + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; +#else + dev->features |= NETIF_F_HW_VLAN_CTAG_RX; +#endif + + return 0; +} + +static int +mvswitch_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + + /* XXX ugly workaround: we can't force the switch + * to gracefully handle hosts moving from one port to another, + * so we have to regularly clear the ATU database */ + + /* wait for the ATU to become available */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + /* flush the ATU */ + w16(pdev, MV_SWITCHREG(ATU_OP), + MV_ATUOP_INPROGRESS | + MV_ATUOP_FLUSH_ALL + ); + + /* wait for operation to complete */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + return 0; +} + +static int +mvswitch_aneg_done(struct phy_device *phydev) +{ + return 1; /* Return any positive value */ +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static void +mvswitch_detach(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + dev->features = priv->orig_features; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + + kfree(priv); +} + +static int +mvswitch_probe(struct phy_device *pdev) +{ + struct mvswitch_priv *priv; + + priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + pdev->priv = priv; + + return 0; +} + +static int +mvswitch_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + if (dev->mdio.addr != 0x10) + return 0; + + reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) + return 0; + + dev->phy_id = MVSWITCH_MAGIC; + return 0; +} + + +static struct phy_driver mvswitch_driver = { + .name = "Marvell 88E6060", + .phy_id = MVSWITCH_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &mvswitch_probe, + .remove = &mvswitch_remove, + .detach = &mvswitch_detach, + .config_init = &mvswitch_config_init, + .config_aneg = &mvswitch_config_aneg, + .aneg_done = &mvswitch_aneg_done, + .read_status = &mvswitch_read_status, +}; + +static int __init +mvswitch_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); + return phy_driver_register(&mvswitch_driver, THIS_MODULE); +} + +static void __exit +mvswitch_exit(void) +{ + phy_driver_unregister(&mvswitch_driver); +} + +module_init(mvswitch_init); +module_exit(mvswitch_exit); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.h b/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.h new file mode 100644 index 0000000000..ab2a1a126e --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/mvswitch.h @@ -0,0 +1,145 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __MVSWITCH_H +#define __MVSWITCH_H + +#define MV_HEADER_SIZE 2 +#define MV_HEADER_PORTS_M 0x001f +#define MV_HEADER_PORTS_S 0 +#define MV_HEADER_VLAN_M 0xf000 +#define MV_HEADER_VLAN_S 12 + +#define MV_TRAILER_SIZE 4 +#define MV_TRAILER_PORTS_M 0x1f +#define MV_TRAILER_PORTS_S 16 +#define MV_TRAILER_FLAGS_S 24 +#define MV_TRAILER_OVERRIDE 0x80 + + +#define MV_PORTS 5 +#define MV_WANPORT 4 +#define MV_CPUPORT 5 + +#define MV_BASE 0x10 + +#define MV_PHYPORT_BASE (MV_BASE + 0x0) +#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) +#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xf) + +enum { + MV_PHY_CONTROL = 0x00, + MV_PHY_STATUS = 0x01, + MV_PHY_IDENT0 = 0x02, + MV_PHY_IDENT1 = 0x03, + MV_PHY_ANEG = 0x04, + MV_PHY_LINK_ABILITY = 0x05, + MV_PHY_ANEG_EXPAND = 0x06, + MV_PHY_XMIT_NEXTP = 0x07, + MV_PHY_LINK_NEXTP = 0x08, + MV_PHY_CONTROL1 = 0x10, + MV_PHY_STATUS1 = 0x11, + MV_PHY_INTR_EN = 0x12, + MV_PHY_INTR_STATUS = 0x13, + MV_PHY_INTR_PORT = 0x14, + MV_PHY_RECV_COUNTER = 0x16, + MV_PHY_LED_PARALLEL = 0x16, + MV_PHY_LED_STREAM = 0x17, + MV_PHY_LED_CTRL = 0x18, + MV_PHY_LED_OVERRIDE = 0x19, + MV_PHY_VCT_CTRL = 0x1a, + MV_PHY_VCT_STATUS = 0x1b, + MV_PHY_CONTROL2 = 0x1e +}; +#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_VLANMAP = 0x06, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ + MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ + MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ + MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ + MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ +}; + +#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) +#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) + +#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ + MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ + MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ + MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ + MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ + MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ + MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ + MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU= (6 << 12), + + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +#define MV_IDENT_MASK 0xfff0 +#define MV_IDENT_VALUE 0x0600 + +#endif diff --git a/target/linux/generic/files-3.18/drivers/net/phy/psb6970.c b/target/linux/generic/files-3.18/drivers/net/phy/psb6970.c new file mode 100644 index 0000000000..c1a381c052 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/psb6970.c @@ -0,0 +1,441 @@ +/* + * Lantiq PSB6970 (Tantos) Switch driver + * + * Copyright (c) 2009,2010 Team Embedded. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * The switch programming done in this driver follows the + * "Ethernet Traffic Separation using VLAN" Application Note as + * published by Lantiq. + */ + +#include +#include +#include +#include + +#define PSB6970_MAX_VLANS 16 +#define PSB6970_NUM_PORTS 7 +#define PSB6970_DEFAULT_PORT_CPU 6 +#define PSB6970_IS_CPU_PORT(x) ((x) > 4) + +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +/* --- Identification --- */ +#define PSB6970_CI0 0x0100 +#define PSB6970_CI0_MASK 0x000f +#define PSB6970_CI1 0x0101 +#define PSB6970_CI1_VAL 0x2599 +#define PSB6970_CI1_MASK 0xffff + +/* --- VLAN filter table --- */ +#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ +#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ + +#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ +#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ + +/* --- Port registers --- */ +#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ +#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ + +#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ +#define PSB6970_PBVM_VMCE (1 << 8) +#define PSB6970_PBVM_AOVTP (1 << 9) +#define PSB6970_PBVM_VSD (1 << 10) +#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ +#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ + +#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ + +struct psb6970_priv { + struct switch_dev dev; + struct phy_device *phy; + u16 (*read) (struct phy_device* phydev, int reg); + void (*write) (struct phy_device* phydev, int reg, u16 val); + struct mutex reg_mutex; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[PSB6970_MAX_VLANS]; + u8 vlan_table[PSB6970_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[PSB6970_NUM_PORTS]; +}; + +#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) + +static u16 psb6970_mii_read(struct phy_device *phydev, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan; + return 0; +} + +static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + + /* make sure no invalid PVIDs get set */ + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static struct switch_attr psb6970_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = psb6970_set_vlan, + .get = psb6970_get_vlan, + .max = 1}, +}; + +static struct switch_attr psb6970_port[] = { +}; + +static struct switch_attr psb6970_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = psb6970_set_vid, + .get = psb6970_get_vid, + .max = 4094, + }, +}; + +static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + priv->vlan_tagged |= (1 << p->id); + else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int psb6970_hw_apply(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i, j; + + mutex_lock(&priv->reg_mutex); + + if (priv->vlan) { + /* into the vlan translation unit */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (vp) { + priv->write(priv->phy, PSB6970_VFxL(j), + PSB6970_VFxL_VV | priv->vlan_id[j]); + priv->write(priv->phy, PSB6970_VFxH(j), + ((vp & priv-> + vlan_tagged) << + PSB6970_VFxH_TM_SHIFT) | vp); + } else /* clear VLAN Valid flag for unused vlans */ + priv->write(priv->phy, PSB6970_VFxL(j), 0); + + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; + + if (priv->vlan) { + ec = PSB6970_EC_IFNTE; + dvid = priv->vlan_id[priv->pvid[i]]; + pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; + + if ((i << 1) & priv->vlan_tagged) + pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; + } + + priv->write(priv->phy, PSB6970_PBVM(i), pbvm); + + if (!PSB6970_IS_CPU_PORT(i)) { + priv->write(priv->phy, PSB6970_EC(i), ec); + priv->write(priv->phy, PSB6970_DVID(i), dvid); + } + } + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int psb6970_reset_switch(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i; + + mutex_lock(&priv->reg_mutex); + + memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - + offsetof(struct psb6970_priv, vlan)); + + for (i = 0; i < PSB6970_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + mutex_unlock(&priv->reg_mutex); + + return psb6970_hw_apply(dev); +} + +static const struct switch_dev_ops psb6970_ops = { + .attr_global = { + .attr = psb6970_globals, + .n_attr = ARRAY_SIZE(psb6970_globals), + }, + .attr_port = { + .attr = psb6970_port, + .n_attr = ARRAY_SIZE(psb6970_port), + }, + .attr_vlan = { + .attr = psb6970_vlan, + .n_attr = ARRAY_SIZE(psb6970_vlan), + }, + .get_port_pvid = psb6970_get_pvid, + .set_port_pvid = psb6970_set_pvid, + .get_vlan_ports = psb6970_get_ports, + .set_vlan_ports = psb6970_set_ports, + .apply_config = psb6970_hw_apply, + .reset_switch = psb6970_reset_switch, +}; + +static int psb6970_config_init(struct phy_device *pdev) +{ + struct psb6970_priv *priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->phy = pdev; + + if (pdev->mdio.addr == 0) + printk(KERN_INFO "%s: psb6970 switch driver attached.\n", + pdev->attached_dev->name); + + if (pdev->mdio.addr != 0) { + kfree(priv); + return 0; + } + + pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; + + mutex_init(&priv->reg_mutex); + priv->read = psb6970_mii_read; + priv->write = psb6970_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; + swdev->ops = &psb6970_ops; + + swdev->name = "Lantiq PSB6970"; + swdev->vlans = PSB6970_MAX_VLANS; + swdev->ports = PSB6970_NUM_PORTS; + + if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { + kfree(priv); + goto done; + } + + ret = psb6970_reset_switch(&priv->dev); + if (ret) { + kfree(priv); + goto done; + } + + dev->phy_ptr = priv; + +done: + return ret; +} + +static int psb6970_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int psb6970_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int psb6970_probe(struct phy_device *pdev) +{ + return 0; +} + +static void psb6970_remove(struct phy_device *pdev) +{ + struct psb6970_priv *priv = pdev->priv; + + if (!priv) + return; + + if (pdev->mdio.addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; + if (reg != PSB6970_CI1_VAL) + return 0; + + dev->phy_id = (reg << 16); + dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; + + return 0; +} + +static struct phy_driver psb6970_driver = { + .name = "Lantiq PSB6970", + .phy_id = PSB6970_CI1_VAL << 16, + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = psb6970_probe, + .remove = psb6970_remove, + .config_init = &psb6970_config_init, + .config_aneg = &psb6970_config_aneg, + .read_status = &psb6970_read_status, +}; + +int __init psb6970_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); + return phy_driver_register(&psb6970_driver, THIS_MODULE); +} + +module_init(psb6970_init); + +void __exit psb6970_exit(void) +{ + phy_driver_unregister(&psb6970_driver); +} + +module_exit(psb6970_exit); + +MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); +MODULE_AUTHOR("Ithamar R. Adema "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8306.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8306.c new file mode 100644 index 0000000000..6d09c1063c --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8306.c @@ -0,0 +1,1066 @@ +/* + * rtl8306.c: RTL8306S switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG 1 + +/* Global (PHY0) */ +#define RTL8306_REG_PAGE 16 +#define RTL8306_REG_PAGE_LO (1 << 15) +#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ + +#define RTL8306_NUM_VLANS 16 +#define RTL8306_NUM_PORTS 6 +#define RTL8306_PORT_CPU 5 +#define RTL8306_NUM_PAGES 4 +#define RTL8306_NUM_REGS 32 + +#define RTL_NAME_S "RTL8306S" +#define RTL_NAME_SD "RTL8306SD" +#define RTL_NAME_SDM "RTL8306SDM" +#define RTL_NAME_UNKNOWN "RTL8306(unknown)" + +#define RTL8306_MAGIC 0x8306 + +static LIST_HEAD(phydevs); + +struct rtl_priv { + struct list_head list; + struct switch_dev dev; + int page; + int type; + int do_cpu; + struct mii_bus *bus; + char hwname[sizeof(RTL_NAME_UNKNOWN)]; + bool fixup; +}; + +struct rtl_phyregs { + int nway; + int speed; + int duplex; +}; + +#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) + +enum { + RTL_TYPE_S, + RTL_TYPE_SD, + RTL_TYPE_SDM, +}; + +struct rtl_reg { + int page; + int phy; + int reg; + int bits; + int shift; + int inverted; +}; + +#define RTL_VLAN_REGOFS(name) \ + (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) + +#define RTL_PORT_REGOFS(name) \ + (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) + +#define RTL_PORT_REG(id, reg) \ + (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) + +#define RTL_VLAN_REG(id, reg) \ + (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) + +#define RTL_GLOBAL_REGATTR(reg) \ + .id = RTL_REG_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = 0, \ + .set = rtl_attr_set_int, \ + .get = rtl_attr_get_int + +#define RTL_PORT_REGATTR(reg) \ + .id = RTL_REG_PORT0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_PORT_REGOFS(reg), \ + .set = rtl_attr_set_port_int, \ + .get = rtl_attr_get_port_int + +#define RTL_VLAN_REGATTR(reg) \ + .id = RTL_REG_VLAN0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_VLAN_REGOFS(reg), \ + .set = rtl_attr_set_vlan_int, \ + .get = rtl_attr_get_vlan_int + +enum rtl_regidx { + RTL_REG_CHIPID, + RTL_REG_CHIPVER, + RTL_REG_CHIPTYPE, + RTL_REG_CPUPORT, + + RTL_REG_EN_CPUPORT, + RTL_REG_EN_TAG_OUT, + RTL_REG_EN_TAG_CLR, + RTL_REG_EN_TAG_IN, + RTL_REG_TRAP_CPU, + RTL_REG_CPU_LINKUP, + RTL_REG_TRUNK_PORTSEL, + RTL_REG_EN_TRUNK, + RTL_REG_RESET, + + RTL_REG_VLAN_ENABLE, + RTL_REG_VLAN_FILTER, + RTL_REG_VLAN_TAG_ONLY, + RTL_REG_VLAN_TAG_AWARE, +#define RTL_VLAN_ENUM(id) \ + RTL_REG_VLAN##id##_VID, \ + RTL_REG_VLAN##id##_PORTMASK + RTL_VLAN_ENUM(0), + RTL_VLAN_ENUM(1), + RTL_VLAN_ENUM(2), + RTL_VLAN_ENUM(3), + RTL_VLAN_ENUM(4), + RTL_VLAN_ENUM(5), + RTL_VLAN_ENUM(6), + RTL_VLAN_ENUM(7), + RTL_VLAN_ENUM(8), + RTL_VLAN_ENUM(9), + RTL_VLAN_ENUM(10), + RTL_VLAN_ENUM(11), + RTL_VLAN_ENUM(12), + RTL_VLAN_ENUM(13), + RTL_VLAN_ENUM(14), + RTL_VLAN_ENUM(15), +#define RTL_PORT_ENUM(id) \ + RTL_REG_PORT##id##_PVID, \ + RTL_REG_PORT##id##_NULL_VID_REPLACE, \ + RTL_REG_PORT##id##_NON_PVID_DISCARD, \ + RTL_REG_PORT##id##_VID_INSERT, \ + RTL_REG_PORT##id##_TAG_INSERT, \ + RTL_REG_PORT##id##_LINK, \ + RTL_REG_PORT##id##_SPEED, \ + RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ + RTL_REG_PORT##id##_DUPLEX, \ + RTL_REG_PORT##id##_RXEN, \ + RTL_REG_PORT##id##_TXEN + RTL_PORT_ENUM(0), + RTL_PORT_ENUM(1), + RTL_PORT_ENUM(2), + RTL_PORT_ENUM(3), + RTL_PORT_ENUM(4), + RTL_PORT_ENUM(5), +}; + +static const struct rtl_reg rtl_regs[] = { + [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, + [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, + [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, + + /* CPU port number */ + [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, + /* Enable CPU port function */ + [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, + /* Enable CPU port tag insertion */ + [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, + /* Enable CPU port tag removal */ + [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, + /* Enable CPU port tag checking */ + [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, + [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, + [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, + [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, + + [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, + [RTL_REG_CPU_LINKUP] = { 0, 6, 22, 1, 15, 0 }, + + [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, + [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, + [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, + [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, + +#define RTL_VLAN_REGS(id, phy, page, regofs) \ + [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ + [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } + RTL_VLAN_REGS( 0, 0, 0, 0), + RTL_VLAN_REGS( 1, 1, 0, 0), + RTL_VLAN_REGS( 2, 2, 0, 0), + RTL_VLAN_REGS( 3, 3, 0, 0), + RTL_VLAN_REGS( 4, 4, 0, 0), + RTL_VLAN_REGS( 5, 0, 1, 2), + RTL_VLAN_REGS( 6, 1, 1, 2), + RTL_VLAN_REGS( 7, 2, 1, 2), + RTL_VLAN_REGS( 8, 3, 1, 2), + RTL_VLAN_REGS( 9, 4, 1, 2), + RTL_VLAN_REGS(10, 0, 1, 4), + RTL_VLAN_REGS(11, 1, 1, 4), + RTL_VLAN_REGS(12, 2, 1, 4), + RTL_VLAN_REGS(13, 3, 1, 4), + RTL_VLAN_REGS(14, 4, 1, 4), + RTL_VLAN_REGS(15, 0, 1, 6), + +#define REG_PORT_SETTING(port, phy) \ + [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ + [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ + [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ + [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ + [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ + [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ + [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } + + REG_PORT_SETTING(0, 0), + REG_PORT_SETTING(1, 1), + REG_PORT_SETTING(2, 2), + REG_PORT_SETTING(3, 3), + REG_PORT_SETTING(4, 4), + REG_PORT_SETTING(5, 6), + +#define REG_PORT_PVID(phy, page, regofs) \ + { page, phy, 24 + regofs, 4, 12, 0 } + [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), + [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), + [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), + [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), + [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), + [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), +}; + + +static inline void +rtl_set_page(struct rtl_priv *priv, unsigned int page) +{ + struct mii_bus *bus = priv->bus; + u16 pgsel; + + if (priv->fixup) + return; + + if (priv->page == page) + return; + + BUG_ON(page > RTL8306_NUM_PAGES); + pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); + pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); + if (page & (1 << 0)) + pgsel |= RTL8306_REG_PAGE_LO; + if (!(page & (1 << 1))) /* bit is inverted */ + pgsel |= RTL8306_REG_PAGE_HI; + bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); +} + +static inline int +rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + bus->write(bus, phy, reg, val); + bus->read(bus, phy, reg); /* flush */ + return 0; +} + +static inline int +rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + return bus->read(bus, phy, reg); +} + +static inline u16 +rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + u16 r; + + rtl_set_page(priv, page); + r = bus->read(bus, phy, reg); + r &= ~mask; + r |= val; + bus->write(bus, phy, reg, r); + return bus->read(bus, phy, reg); /* flush */ +} + + +static inline int +rtl_get(struct switch_dev *dev, enum rtl_regidx s) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 val; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + if (r->bits == 0) /* unimplemented */ + return 0; + + val = rtl_r16(dev, r->page, r->phy, r->reg); + + if (r->shift > 0) + val >>= r->shift; + + if (r->inverted) + val = ~val; + + val &= (1 << r->bits) - 1; + + return val; +} + +static int +rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 mask = 0xffff; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + + if (r->bits == 0) /* unimplemented */ + return 0; + + if (r->shift > 0) + val <<= r->shift; + + if (r->inverted) + val = ~val; + + if (r->bits != 16) { + mask = (1 << r->bits) - 1; + mask <<= r->shift; + } + val &= mask; + return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); +} + +static void +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); +} + +static void +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); +} + +static void +rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) +{ + rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); + rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); +} + +static int +rtl_hw_apply(struct switch_dev *dev) +{ + int i; + int trunk_en, trunk_psel; + struct rtl_phyregs port5; + + rtl_phy_save(dev, 5, &port5); + + /* disable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 0); + } + + /* save trunking status */ + trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); + trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); + + /* trunk port 3 and 4 + * XXX: Big WTF, but RealTek seems to do it */ + rtl_set(dev, RTL_REG_EN_TRUNK, 1); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); + + /* execute the software reset */ + rtl_set(dev, RTL_REG_RESET, 1); + + /* wait for the reset to complete, + * but don't wait for too long */ + for (i = 0; i < 10; i++) { + if (rtl_get(dev, RTL_REG_RESET) == 0) + break; + + msleep(1); + } + + /* enable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 1); + } + + /* restore trunking settings */ + rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); + rtl_phy_restore(dev, 5, &port5); + + rtl_set(dev, RTL_REG_CPU_LINKUP, 1); + + return 0; +} + +static void +rtl_hw_init(struct switch_dev *dev) +{ + struct rtl_priv *priv = to_rtl(dev); + int cpu_mask = 1 << dev->cpu_port; + int i; + + rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); + rtl_set(dev, RTL_REG_VLAN_FILTER, 0); + rtl_set(dev, RTL_REG_EN_TRUNK, 0); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); + + /* initialize cpu port settings */ + if (priv->do_cpu) { + rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); + rtl_set(dev, RTL_REG_EN_CPUPORT, 1); + } else { + rtl_set(dev, RTL_REG_CPUPORT, 7); + rtl_set(dev, RTL_REG_EN_CPUPORT, 0); + } + rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); + rtl_set(dev, RTL_REG_EN_TAG_IN, 0); + rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); + + /* reset all vlans */ + for (i = 0; i < RTL8306_NUM_VLANS; i++) { + rtl_set(dev, RTL_VLAN_REG(i, VID), i); + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); + } + + /* default to port isolation */ + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + unsigned long mask; + + if ((1 << i) == cpu_mask) + mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ + else + mask = cpu_mask | (1 << i); + + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); + rtl_set(dev, RTL_PORT_REG(i, PVID), i); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); + } + rtl_hw_apply(dev); +} + +#ifdef DEBUG +static int +rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + priv->do_cpu = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + val->value.i = priv->do_cpu; + return 0; +} + +static int +rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + dev->cpu_port = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = dev->cpu_port; + return 0; +} +#endif + +static int +rtl_reset(struct switch_dev *dev) +{ + rtl_hw_init(dev); + return 0; +} + +static int +rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; + + if (attr->id >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + if ((attr->max > 0) && (val->value.i > attr->max)) + return -EINVAL; + + /* access to phy register 22 on port 4/5 + * needs phy status save/restore */ + if ((val->port_vlan > 3) && + (rtl_regs[idx].reg == 22) && + (rtl_regs[idx].page == 0)) { + + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, idx, val->value.i); + rtl_phy_restore(dev, val->port_vlan, &port); + } else { + rtl_set(dev, idx, val->value.i); + } + + return 0; +} + +static int +rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + + if (idx >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + val->value.i = rtl_get(dev, idx); + return 0; +} + +static int +rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link) +{ + if (port >= RTL8306_NUM_PORTS) + return -EINVAL; + + /* in case the link changes from down to up, the register is only updated on read */ + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + if (!link->link) + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + + if (!link->link) + return 0; + + link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); + link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + + if (rtl_get(dev, RTL_PORT_REG(port, SPEED))) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int i, mask; + + mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + struct switch_port *port; + + if (!(mask & (1 << i))) + continue; + + port = &val->value.ports[val->len]; + port->id = i; + if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port) + port->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; + int en = val->value.i; + int i; + + rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); + rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); + if (en) + rtl_set(dev, RTL_REG_VLAN_FILTER, en); + + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (i > 3) + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); + if (i > 3) + rtl_phy_restore(dev, val->port_vlan, &port); + } + rtl_set(dev, RTL_REG_VLAN_ENABLE, en); + + return 0; +} + +static int +rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE); + return 0; +} + +static int +rtl_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int mask = 0; + unsigned int oldmask; + int i; + + for(i = 0; i < val->len; i++) + { + struct switch_port *port = &val->value.ports[i]; + bool tagged = false; + + mask |= (1 << port->id); + + if (port->id == dev->cpu_port) + continue; + + if ((i == dev->cpu_port) || + (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + tagged = true; + + /* fix up PVIDs for added ports */ + if (!tagged) + rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); + + rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); + } + + oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); + + /* fix up PVIDs for removed ports, default to last vlan */ + oldmask &= ~mask; + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (!(oldmask & (1 << i))) + continue; + + if (i == dev->cpu_port) + continue; + + if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) + rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); + } + + return 0; +} + +static struct switch_attr rtl_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .max = 1, + .set = rtl_set_vlan, + .get = rtl_get_vlan, + }, + { + RTL_GLOBAL_REGATTR(EN_TRUNK), + .name = "trunk", + .description = "Enable port trunking", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), + .name = "trunk_sel", + .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", + .max = 1, + }, +#ifdef DEBUG + { + RTL_GLOBAL_REGATTR(VLAN_FILTER), + .name = "vlan_filter", + .description = "Filter incoming packets for allowed VLANS", + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "cpuport", + .description = "CPU Port", + .set = rtl_set_cpuport, + .get = rtl_get_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + .type = SWITCH_TYPE_INT, + .name = "use_cpuport", + .description = "CPU Port handling flag", + .set = rtl_set_use_cpuport, + .get = rtl_get_use_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + RTL_GLOBAL_REGATTR(TRAP_CPU), + .name = "trap_cpu", + .description = "VLAN trap to CPU", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), + .name = "vlan_tag_aware", + .description = "Enable VLAN tag awareness", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), + .name = "tag_only", + .description = "Only accept tagged packets", + .max = 1, + }, +#endif +}; +static struct switch_attr rtl_port[] = { + { + RTL_PORT_REGATTR(PVID), + .name = "pvid", + .description = "Port VLAN ID", + .max = RTL8306_NUM_VLANS - 1, + }, +#ifdef DEBUG + { + RTL_PORT_REGATTR(NULL_VID_REPLACE), + .name = "null_vid", + .description = "NULL VID gets replaced by port default vid", + .max = 1, + }, + { + RTL_PORT_REGATTR(NON_PVID_DISCARD), + .name = "non_pvid_discard", + .description = "discard packets with VID != PVID", + .max = 1, + }, + { + RTL_PORT_REGATTR(VID_INSERT), + .name = "vid_insert_remove", + .description = "how should the switch insert and remove vids ?", + .max = 3, + }, + { + RTL_PORT_REGATTR(TAG_INSERT), + .name = "tag_insert", + .description = "tag insertion handling", + .max = 3, + }, +#endif +}; + +static struct switch_attr rtl_vlan[] = { + { + RTL_VLAN_REGATTR(VID), + .name = "vid", + .description = "VLAN ID (1-4095)", + .max = 4095, + }, +}; + +static const struct switch_dev_ops rtl8306_ops = { + .attr_global = { + .attr = rtl_globals, + .n_attr = ARRAY_SIZE(rtl_globals), + }, + .attr_port = { + .attr = rtl_port, + .n_attr = ARRAY_SIZE(rtl_port), + }, + .attr_vlan = { + .attr = rtl_vlan, + .n_attr = ARRAY_SIZE(rtl_vlan), + }, + + .get_vlan_ports = rtl_get_ports, + .set_vlan_ports = rtl_set_ports, + .apply_config = rtl_hw_apply, + .reset_switch = rtl_reset, + .get_port_link = rtl_get_port_link, +}; + +static int +rtl8306_config_init(struct phy_device *pdev) +{ + struct net_device *netdev = pdev->attached_dev; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + struct switch_val val; + unsigned int chipid, chipver, chiptype; + int err; + + /* Only init the switch for the primary PHY */ + if (pdev->mdio.addr != 0) + return 0; + + val.value.i = 1; + priv->dev.cpu_port = RTL8306_PORT_CPU; + priv->dev.ports = RTL8306_NUM_PORTS; + priv->dev.vlans = RTL8306_NUM_VLANS; + priv->dev.ops = &rtl8306_ops; + priv->do_cpu = 0; + priv->page = -1; + priv->bus = pdev->mdio.bus; + + chipid = rtl_get(dev, RTL_REG_CHIPID); + chipver = rtl_get(dev, RTL_REG_CHIPVER); + chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); + switch(chiptype) { + case 0: + case 2: + strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); + priv->type = RTL_TYPE_S; + break; + case 1: + strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SD; + break; + case 3: + strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SDM; + break; + default: + strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); + break; + } + + dev->name = priv->hwname; + rtl_hw_init(dev); + + printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); + + err = register_switch(dev, netdev); + if (err < 0) { + kfree(priv); + return err; + } + + return 0; +} + + +static int +rtl8306_fixup(struct phy_device *pdev) +{ + struct rtl_priv priv; + u16 chipid; + + /* Attach to primary LAN port and WAN port */ + if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4) + return 0; + + memset(&priv, 0, sizeof(priv)); + priv.fixup = true; + priv.page = -1; + priv.bus = pdev->mdio.bus; + chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); + if (chipid == 0x5988) + pdev->phy_id = RTL8306_MAGIC; + + return 0; +} + +static int +rtl8306_probe(struct phy_device *pdev) +{ + struct rtl_priv *priv; + + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->mdio.bus) + goto found; + } + priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = pdev->mdio.bus; + +found: + pdev->priv = priv; + return 0; +} + +static void +rtl8306_remove(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + unregister_switch(&priv->dev); + kfree(priv); +} + +static int +rtl8306_config_aneg(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->mdio.addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + + return 0; +} + +static int +rtl8306_read_status(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->mdio.addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } + + return 0; +} + + +static struct phy_driver rtl8306_driver = { + .name = "Realtek RTL8306S", +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0)) + .flags = PHY_HAS_MAGICANEG, +#endif + .phy_id = RTL8306_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &rtl8306_probe, + .remove = &rtl8306_remove, + .config_init = &rtl8306_config_init, + .config_aneg = &rtl8306_config_aneg, + .read_status = &rtl8306_read_status, +}; + + +static int __init +rtl_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); + return phy_driver_register(&rtl8306_driver, THIS_MODULE); +} + +static void __exit +rtl_exit(void) +{ + phy_driver_unregister(&rtl8306_driver); +} + +module_init(rtl_init); +module_exit(rtl_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 0000000000..c0cb680e23 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,1628 @@ +/* + * Realtek RTL8366 SMI interface driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +#include +#endif + +#include "rtl8366_smi.h" + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 + +#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ +#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = !!gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { + dev_err(smi->parent, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return 0; +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +static int __rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} +/* Read/write via mdiobus */ +#define MDC_MDIO_CTRL0_REG 31 +#define MDC_MDIO_START_REG 29 +#define MDC_MDIO_CTRL1_REG 21 +#define MDC_MDIO_ADDRESS_REG 23 +#define MDC_MDIO_DATA_WRITE_REG 24 +#define MDC_MDIO_DATA_READ_REG 25 + +#define MDC_MDIO_START_OP 0xFFFF +#define MDC_MDIO_ADDR_OP 0x000E +#define MDC_MDIO_READ_OP 0x0001 +#define MDC_MDIO_WRITE_OP 0x0003 +#define MDC_REALTEK_PHY_ADDR 0x0 + +int __rtl8366_mdio_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write read control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_READ_OP); + + /* Write Start command to register 29 */ + mbus->write(smi->ext_mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Read data from register 25 */ + *data = mbus->read(mbus, phy_id, MDC_MDIO_DATA_READ_REG); + + mutex_unlock(&mbus->mdio_lock); + + return 0; +} + +static int __rtl8366_mdio_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data to register 24 */ + mbus->write(mbus, phy_id, MDC_MDIO_DATA_WRITE_REG, data); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_WRITE_OP); + + mutex_unlock(&mbus->mdio_lock); + return 0; +} + +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_read_reg(smi, addr, data); + else + return __rtl8366_smi_read_reg(smi, addr, data); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); + +static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + if (ack) + ret = rtl8366_smi_write_byte(smi, data >> 8); + else + ret = rtl8366_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_write_reg(smi, addr, data); + else + return __rtl8366_smi_write_reg(smi, addr, data, true); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); + +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); + +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) +{ + u32 t; + int err; + + err = rtl8366_smi_read_reg(smi, addr, &t); + if (err) + return err; + + err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); + return err; + +} +EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); + +static int rtl8366_reset(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) { + smi->hw_reset(smi, true); + msleep(RTL8366_SMI_HW_STOP_DELAY); + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + return 0; + } + + return smi->ops->reset_chip(smi); +} + +static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) +{ + int err; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + err = smi->ops->get_mc_index(smi, i, &index); + if (err) + return err; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} + +static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Update the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + err = smi->ops->set_vlan_4k(smi, &vlan4k); + if (err) + return err; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return err; +} + +static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int index; + + err = smi->ops->get_mc_index(smi, port, &index); + if (err) + return err; + + err = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (err) + return err; + + *val = vlanmc.vid; + return 0; +} + +static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, + unsigned vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + err = rtl8366_mc_is_used(smi, i, &used); + if (err) + return err; + + if (!used) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + dev_err(smi->parent, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} + +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + int err; + + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + + if (!enable) { + smi->vlan4k_enabled = 0; + err = smi->ops->enable_vlan4k(smi, enable); + } + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + int err; + + if (enable) { + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + } + + err = smi->ops->enable_vlan4k(smi, enable); + if (err) + return err; + + smi->vlan4k_enabled = enable; + return 0; +} + +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) +{ + int port; + int err; + + for (port = 0; port < smi->num_ports; port++) { + err = smi->ops->enable_port(smi, port, enable); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int i; + + rtl8366_enable_vlan(smi, 0); + rtl8366_enable_vlan4k(smi, 0); + + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +static int rtl8366_init_vlan(struct rtl8366_smi *smi) +{ + int port; + int err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + mask = (1 << smi->num_ports) - 1; + else + mask = (1 << port) | (1 << smi->cpu_port); + + err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (err) + return err; + + err = rtl8366_set_pvid(smi, port, (port + 1)); + if (err) + return err; + } + + return rtl8366_enable_vlan(smi, 1); +} + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); + +static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2s %6s %4s %6s %6s %3s\n", + "id", "vid","prio", "member", "untag", "fid"); + + for (i = 0; i < smi->num_vlan_mc; ++i) { + struct rtl8366_vlan_mc vlanmc; + + smi->ops->get_vlan_mc(smi, i, &vlanmc); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2d %6d %4d 0x%04x 0x%04x %3d\n", + i, vlanmc.vid, vlanmc.priority, + vlanmc.member, vlanmc.untag, vlanmc.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define RTL8366_VLAN4K_PAGE_SIZE 64 +#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) + +static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + int offset; + char *buf = smi->buf; + + if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "invalid page: %u\n", smi->dbg_vlan_4k_page); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4s %6s %6s %3s\n", + "vid", "member", "untag", "fid"); + + offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; + for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { + struct rtl8366_vlan_4k vlan4k; + + smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d 0x%04x 0x%04x %3d\n", + vlan4k.vid, vlan4k.member, + vlan4k.untag, vlan4k.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_pvid(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + char *buf = smi->buf; + int len = 0; + int i; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", + "port", "pvid"); + + for (i = 0; i < smi->num_ports; i++) { + int pvid; + int err; + + err = rtl8366_get_pvid(smi, i, &pvid); + if (err) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d error\n", i); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d %4d\n", i, pvid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_reg(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + u32 t, reg = smi->dbg_reg; + int err, len = 0; + char *buf = smi->buf; + + memset(buf, '\0', sizeof(smi->buf)); + + err = rtl8366_smi_read_reg(smi, reg, &t); + if (err) { + len += snprintf(buf, sizeof(smi->buf), + "Read failed (reg: 0x%04x)\n", reg); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", + reg, t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_write_debugfs_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + unsigned long data; + u32 reg = smi->dbg_reg; + int err; + size_t len; + char *buf = smi->buf; + + len = min(count, sizeof(smi->buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + dev_err(smi->parent, "copy from user failed\n"); + return -EFAULT; + } + + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + + if (kstrtoul(buf, 16, &data)) { + dev_err(smi->parent, "Invalid reg value %s\n", buf); + } else { + err = rtl8366_smi_write_reg(smi, reg, data); + if (err) { + dev_err(smi->parent, + "writing reg 0x%04x val 0x%04lx failed\n", + reg, data); + } + } + + return count; +} + +static ssize_t rtl8366_read_debugfs_mibs(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = file->private_data; + int i, j, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", + "Counter"); + + for (i = 0; i < smi->num_ports; i++) { + char port_buf[10]; + + snprintf(port_buf, sizeof(port_buf), "Port %d", i); + len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", + port_buf); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + + for (i = 0; i < smi->num_mib_counters; i++) { + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", + smi->mib_counters[i].name); + for (j = 0; j < smi->num_ports; j++) { + unsigned long long counter = 0; + + if (!smi->ops->get_mib_counter(smi, i, j, &counter)) + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12llu ", counter); + else + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12s ", "error"); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_rtl8366_regs = { + .read = rtl8366_read_debugfs_reg, + .write = rtl8366_write_debugfs_reg, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_mc = { + .read = rtl8366_read_debugfs_vlan_mc, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_4k = { + .read = rtl8366_read_debugfs_vlan_4k, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_pvid = { + .read = rtl8366_read_debugfs_pvid, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_mibs = { + .read = rtl8366_read_debugfs_mibs, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static void rtl8366_debugfs_init(struct rtl8366_smi *smi) +{ + struct dentry *node; + struct dentry *root; + + if (!smi->debugfs_root) + smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), + NULL); + + if (!smi->debugfs_root) { + dev_err(smi->parent, "Unable to create debugfs dir\n"); + return; + } + root = smi->debugfs_root; + + node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, + &smi->dbg_reg); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "reg"); + return; + } + + node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, + &fops_rtl8366_regs); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "val"); + return; + } + + node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, + &fops_rtl8366_vlan_mc); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_mc"); + return; + } + + node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, + &smi->dbg_vlan_4k_page); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k_page"); + return; + } + + node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, + &fops_rtl8366_vlan_4k); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k"); + return; + } + + node = debugfs_create_file("pvid", S_IRUSR, root, smi, + &fops_rtl8366_pvid); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "pvid"); + return; + } + + node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, + &fops_rtl8366_mibs); + if (!node) + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "mibs"); +} + +static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) +{ + if (smi->debugfs_root) { + debugfs_remove_recursive(smi->debugfs_root); + smi->debugfs_root = NULL; + } +} +#else +static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} +static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} +#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + +#ifdef CONFIG_OF + struct device_node *np = NULL; + + np = of_get_child_by_name(smi->parent->of_node, "mdio-bus"); +#endif + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = dev_name(smi->parent); + smi->mii_bus->read = smi->ops->mii_read; + smi->mii_bus->write = smi->ops->mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(smi->parent)); + smi->mii_bus->parent = smi->parent; + smi->mii_bus->phy_mask = ~(0x1f); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) + { + int i; + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + } +#endif + +#ifdef CONFIG_OF + if (np) + ret = of_mdiobus_register(smi->mii_bus, np); + else +#endif + ret = mdiobus_register(smi->mii_bus); + + if (ret) + goto err_free; + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + err = rtl8366_reset(smi); + if (err) + return err; + + err = smi->ops->setup(smi); + if (err) + return err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + err = rtl8366_enable_vlan(smi, 1); + if (err) + return err; + + return rtl8366_enable_all_ports(smi, 1); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); + +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_get_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); + +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_set_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); + +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int i, len = 0; + unsigned long long counter = 0; + char *buf = smi->buf; + + if (val->port_vlan >= smi->num_ports) + return -EINVAL; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "Port %d MIB counters\n", + val->port_vlan); + + for (i = 0; i < smi->num_mib_counters; ++i) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%-36s: ", smi->mib_counters[i].name); + if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, + &counter)) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%llu\n", counter); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%s\n", "error"); + } + + val->value.s = buf; + val->len = len; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); + +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + unsigned long long counter = 0; + int ret; + + if (port >= smi->num_ports) + return -EINVAL; + + ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter); + if (ret) + return ret; + + stats->tx_bytes = counter; + + ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter); + if (ret) + return ret; + + stats->rx_bytes = counter; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats); + +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int i; + u32 len = 0; + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char *buf = smi->buf; + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + memset(buf, '\0', sizeof(smi->buf)); + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "VLAN %d: Ports: '", vlan4k.vid); + + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & (1 << i))) + continue; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, + (vlan4k.untag & (1 << i)) ? "" : "t"); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "', members=%04x, untag=%04x, fid=%u", + vlan4k.member, vlan4k.untag, vlan4k.fid); + + val->value.s = buf; + val->len = len; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); + +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + struct rtl8366_vlan_4k vlan4k; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + + port = &val->value.ports[0]; + val->len = 0; + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & BIT(i))) + continue; + + port->id = i; + port->flags = (vlan4k.untag & BIT(i)) ? + 0 : BIT(SWITCH_PORT_FLAG_TAGGED); + val->len++; + port++; + } + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); + +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + u32 member = 0; + u32 untag = 0; + int err; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + port = &val->value.ports[0]; + for (i = 0; i < val->len; i++, port++) { + int pvid = 0; + member |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) + untag |= BIT(port->id); + + /* + * To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + err = rtl8366_get_pvid(smi, port->id, &pvid); + if (err < 0) + return err; + if (pvid == 0) { + err = rtl8366_set_pvid(smi, port->id, val->port_vlan); + if (err < 0) + return err; + } + } + + return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); + +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + val->value.i = vlan4k.fid; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); + +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + if (val->value.i < 0 || val->value.i > attr->max) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + return rtl8366_set_vlan(smi, val->port_vlan, + vlan4k.member, + vlan4k.untag, + val->value.i); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); + +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + val->value.i = smi->vlan_enabled; + else + val->value.i = smi->vlan4k_enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); + +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + err = rtl8366_enable_vlan(smi, val->value.i); + else + err = rtl8366_enable_vlan4k(smi, val->value.i); + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) +{ + struct rtl8366_smi *smi; + + BUG_ON(!parent); + + smi = kzalloc(sizeof(*smi), GFP_KERNEL); + if (!smi) { + dev_err(parent, "no memory for private data\n"); + return NULL; + } + + smi->parent = parent; + return smi; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); + +static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) +{ + int err; + + if (!smi->ext_mbus) { + err = gpio_request(smi->gpio_sda, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sda, err); + goto err_out; + } + + err = gpio_request(smi->gpio_sck, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sck, err); + goto err_free_sda; + } + } + + spin_lock_init(&smi->lock); + + /* start the switch */ + if (smi->hw_reset) { + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + } + + return 0; + + err_free_sda: + gpio_free(smi->gpio_sda); + err_out: + return err; +} + +static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) + smi->hw_reset(smi, true); + + if (!smi->ext_mbus) { + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); + } +} + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) +{ + static struct rtl8366_smi smi; + enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; + u32 reg = 0; + + memset(&smi, 0, sizeof(smi)); + smi.gpio_sda = pdata->gpio_sda; + smi.gpio_sck = pdata->gpio_sck; + smi.clk_delay = 10; + smi.cmd_read = 0xa9; + smi.cmd_write = 0xa8; + + if (__rtl8366_smi_init(&smi, "rtl8366")) + goto out; + + if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) + goto cleanup; + + switch(reg) { + case 0x6027: + printk("Found an RTL8366S switch\n"); + type = RTL8366_TYPE_S; + break; + case 0x5937: + printk("Found an RTL8366RB switch\n"); + type = RTL8366_TYPE_RB; + break; + default: + printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); + break; + } + +cleanup: + __rtl8366_smi_cleanup(&smi); +out: + return type; +} + +int rtl8366_smi_init(struct rtl8366_smi *smi) +{ + int err; + + if (!smi->ops) + return -EINVAL; + + err = __rtl8366_smi_init(smi, dev_name(smi->parent)); + if (err) + goto err_out; + + if (!smi->ext_mbus) + dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", + smi->gpio_sda, smi->gpio_sck); + else + dev_info(smi->parent, "using MDIO bus '%s'\n", smi->ext_mbus->name); + + err = smi->ops->detect(smi); + if (err) { + dev_err(smi->parent, "chip detection failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_reset(smi); + if (err) + goto err_free_sck; + + err = smi->ops->setup(smi); + if (err) { + dev_err(smi->parent, "chip setup failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_init_vlan(smi); + if (err) { + dev_err(smi->parent, "VLAN initialization failed, err=%d\n", + err); + goto err_free_sck; + } + + err = rtl8366_enable_all_ports(smi, 1); + if (err) + goto err_free_sck; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_free_sck; + + rtl8366_debugfs_init(smi); + + return 0; + + err_free_sck: + __rtl8366_smi_cleanup(smi); + err_out: + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_init); + +void rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + rtl8366_debugfs_remove(smi); + rtl8366_smi_mii_cleanup(smi); + __rtl8366_smi_cleanup(smi); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); + +#ifdef CONFIG_OF +static void rtl8366_smi_reset(struct rtl8366_smi *smi, bool active) +{ + if (active) + reset_control_assert(smi->reset); + else + reset_control_deassert(smi->reset); +} + +int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0); + int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0); + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio_node; + + mdio_node = of_parse_phandle(np, "mii-bus", 0); + if (!mdio_node) { + dev_err(&pdev->dev, "cannot find mdio node phandle"); + goto try_gpio; + } + + smi->ext_mbus = of_mdio_find_bus(mdio_node); + if (!smi->ext_mbus) { + dev_err(&pdev->dev, + "cannot find mdio bus from bus handle"); + goto try_gpio; + } + + return 0; + +try_gpio: + if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) { + dev_err(&pdev->dev, "gpios missing in devictree\n"); + return -EINVAL; + } + + smi->gpio_sda = sda; + smi->gpio_sck = sck; + smi->reset = devm_reset_control_get(&pdev->dev, "switch"); + if (!IS_ERR(smi->reset)) + smi->hw_reset = rtl8366_smi_reset; + + return 0; +} +#else +static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + return -ENODEV; +} +#endif + +int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata = pdev->dev.platform_data; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + return 0; +} + + +struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) + return NULL; + + if (pdev->dev.of_node) + err = rtl8366_smi_probe_of(pdev, smi); + else + err = rtl8366_smi_probe_plat(pdev, smi); + + if (err) + goto free_smi; + + return smi; + +free_smi: + kfree(smi); + return NULL; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_probe); + +MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.h b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.h new file mode 100644 index 0000000000..d1d988a372 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366_smi.h @@ -0,0 +1,160 @@ +/* + * Realtek RTL8366 SMI interface driver defines + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_SMI_H +#define _RTL8366_SMI_H + +#include +#include +#include +#include + +struct rtl8366_smi_ops; +struct rtl8366_vlan_ops; +struct mii_bus; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned base; + unsigned offset; + unsigned length; + const char *name; +}; + +struct rtl8366_smi { + struct device *parent; + unsigned int gpio_sda; + unsigned int gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + unsigned int clk_delay; /* ns */ + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + struct switch_dev sw_dev; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + struct rtl8366_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; + + struct reset_control *reset; + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS + struct dentry *debugfs_root; + u16 dbg_reg; + u8 dbg_vlan_4k_page; +#endif + struct mii_bus *ext_mbus; +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct rtl8366_smi_ops { + int (*detect)(struct rtl8366_smi *smi); + int (*reset_chip)(struct rtl8366_smi *smi); + int (*setup)(struct rtl8366_smi *smi); + + int (*mii_read)(struct mii_bus *bus, int addr, int reg); + int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); + + int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); + int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); + int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val); + int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); + int (*enable_vlan)(struct rtl8366_smi *smi, int enable); + int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); + int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); +}; + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); +int rtl8366_smi_init(struct rtl8366_smi *smi); +void rtl8366_smi_cleanup(struct rtl8366_smi *smi); +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi); +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file); +#endif + +static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) +{ + return container_of(sw, struct rtl8366_smi, sw_dev); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev); +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id); + +struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev); + +#endif /* _RTL8366_SMI_H */ diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8366rb.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366rb.c new file mode 100644 index 0000000000..dc394c02b6 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366rb.c @@ -0,0 +1,1532 @@ +/* + * Platform driver for the Realtek RTL8366RB ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" +#define RTL8366RB_DRIVER_VER "0.2.4" + +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_PAGE_MAX 7 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Port Mirror Control Register */ +#define RTL8366RB_PMCR 0x0007 +#define RTL8366RB_PMCR_SOURCE_PORT(_x) (_x) +#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f +#define RTL8366RB_PMCR_MONITOR_PORT(_x) ((_x) << 4) +#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 +#define RTL8366RB_PMCR_MIRROR_RX BIT(8) +#define RTL8366RB_PMCR_MIRROR_TX BIT(9) +#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) +#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW 1 +#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 + +#define RTL8366RB_PHY_CTRL_READ 1 +#define RTL8366RB_PHY_CTRL_WRITE 0 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_BIT 0 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + + +#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +#define RTL8366RB_VLAN_FID_MASK 0x7 + + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG_OFFSET 14 +#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG_OFFSET 9 +#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) + +#define RTL8366RB_BDTH_SW_MAX 1048512 +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS_BIT 15 +#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + + +#define RTL8366RB_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366RB_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_setup(struct rtl8366_smi *smi) +{ + int err; +#ifdef CONFIG_OF + unsigned i; + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; + + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) + return -EINVAL; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + rtl8366rb_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; + +} + +static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_GLOBAL_RESET); +} + +static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366RB_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, portmask); + + return 0; +} + +static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { + reg = RTL8366RB_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366RB_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 mask, data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + mask = 1 << val->port_vlan ; + if (val->value.i) + data = mask; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); +} + +static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); + if (data & (1 << val->port_vlan)) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), + RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, + val->value.i | + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); + +} + +static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); + data &= RTL8366RB_IB_BDTH_MASK; + if (data < RTL8366RB_IB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, + RTL8366RB_EB_PREIFG_MASK, + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), + RTL8366RB_EB_BDTH_MASK, val->value.i ); + +} + +static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); + data &= RTL8366RB_EB_BDTH_MASK; + if (data < RTL8366RB_EB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_QOS_MASK; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); +} + +static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); + if (data & RTL8366RB_QOS_MASK) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_RX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data); +} + +static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_RX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_TX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data); +} + +static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_TX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_ISO; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data); +} + +static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_ISO) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_SPC; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data); +} + +static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_SPC) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK; + + return 0; +} + +static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); +} + +static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366rb_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366rb_sw_set_learning_enable, + .get = rtl8366rb_sw_get_learning_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366rb_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366rb_sw_set_blinkrate, + .get = rtl8366rb_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_qos", + .description = "Enable QOS", + .set = rtl8366rb_sw_set_qos_enable, + .get = rtl8366rb_sw_get_qos_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = rtl8366rb_sw_set_mirror_rx_enable, + .get = rtl8366rb_sw_get_mirror_rx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = rtl8366rb_sw_set_mirror_tx_enable, + .get = rtl8366rb_sw_get_mirror_tx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_monitor_isolation", + .description = "Enable isolation of monitor port (TX packets will be dropped)", + .set = rtl8366rb_sw_set_monitor_isolation_enable, + .get = rtl8366rb_sw_get_monitor_isolation_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_pause_frames", + .description = "Enable mirroring of RX pause frames", + .set = rtl8366rb_sw_set_mirror_pause_frames_enable, + .get = rtl8366rb_sw_get_mirror_pause_frames_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = rtl8366rb_sw_set_mirror_monitor_port, + .get = rtl8366rb_sw_get_mirror_monitor_port, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = rtl8366rb_sw_set_mirror_source_port, + .get = rtl8366rb_sw_get_mirror_source_port, + .max = 5 + }, +}; + +static struct switch_attr rtl8366rb_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366rb_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366rb_sw_set_port_led, + .get = rtl8366rb_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "disable", + .description = "Get/Set port state (enabled or disabled)", + .max = 1, + .set = rtl8366rb_sw_set_port_disable, + .get = rtl8366rb_sw_get_port_disable, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_in", + .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_in, + .get = rtl8366rb_sw_get_port_rate_in, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_out", + .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_out, + .get = rtl8366rb_sw_get_port_rate_out, + }, +}; + +static struct switch_attr rtl8366rb_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366RB_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366rb_globals, + .n_attr = ARRAY_SIZE(rtl8366rb_globals), + }, + .attr_port = { + .attr = rtl8366rb_port, + .n_attr = ARRAY_SIZE(rtl8366rb_port), + }, + .attr_vlan = { + .attr = rtl8366rb_vlan, + .n_attr = ARRAY_SIZE(rtl8366rb_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366rb_sw_get_port_link, + .get_port_stats = rtl8366rb_sw_get_port_stats, +}; + +static int rtl8366rb_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366RB"; + dev->cpu_port = RTL8366RB_PORT_NUM_CPU; + dev->ports = RTL8366RB_NUM_PORTS; + dev->vlans = RTL8366RB_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366rb_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .reset_chip = rtl8366rb_reset_chip, + .setup = rtl8366rb_setup, + + .mii_read = rtl8366rb_mii_read, + .mii_write = rtl8366rb_mii_write, + + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .enable_port = rtl8366rb_enable_port, +}; + +static int rtl8366rb_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366RB_DRIVER_DESC + " version " RTL8366RB_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366rb_smi_ops; + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366rb_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366rb_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366rb_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366rb_match[] = { + { .compatible = "realtek,rtl8366rb" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366rb_match); +#endif + +static struct platform_driver rtl8366rb_driver = { + .driver = { + .name = RTL8366RB_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rtl8366rb_match), + }, + .probe = rtl8366rb_probe, + .remove = rtl8366rb_remove, +}; + +static int __init rtl8366rb_module_init(void) +{ + return platform_driver_register(&rtl8366rb_driver); +} +module_init(rtl8366rb_module_init); + +static void __exit rtl8366rb_module_exit(void) +{ + platform_driver_unregister(&rtl8366rb_driver); +} +module_exit(rtl8366rb_module_exit); + +MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); +MODULE_VERSION(RTL8366RB_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_AUTHOR("Roman Yeryomin "); +MODULE_AUTHOR("Colin Leitner "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8366s.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366s.c new file mode 100644 index 0000000000..3f458f9dec --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8366s.c @@ -0,0 +1,1320 @@ +/* + * Platform driver for the Realtek RTL8366S ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" +#define RTL8366S_DRIVER_VER "0.2.2" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366S_SGCR 0x0000 +#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) +#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) +#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) +#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_EN_VLAN BIT(13) + +/* Port Enable Control register */ +#define RTL8366S_PECR 0x0001 + +/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */ +#define RTL8366S_GREEN_ETHERNET_CTRL_REG 0x000a +#define RTL8366S_GREEN_ETHERNET_CTRL_MASK 0x0018 +#define RTL8366S_GREEN_ETHERNET_TX_BIT (1 << 3) +#define RTL8366S_GREEN_ETHERNET_RX_BIT (1 << 4) + +/* Switch Security Control registers */ +#define RTL8366S_SSCR0 0x0002 +#define RTL8366S_SSCR1 0x0003 +#define RTL8366S_SSCR2 0x0004 +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366S_RESET_CTRL_REG 0x0100 +#define RTL8366S_CHIP_CTRL_RESET_HW 1 +#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +/* Green Ethernet Feature for PHY ports */ +#define RTL8366S_PHY_POWER_SAVING_CTRL_REG 12 +#define RTL8366S_PHY_POWER_SAVING_MASK 0x1000 + +/* LED control registers */ +#define RTL8366S_LED_BLINKRATE_REG 0x0420 +#define RTL8366S_LED_BLINKRATE_BIT 0 +#define RTL8366S_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366S_LED_CTRL_REG 0x0421 +#define RTL8366S_LED_0_1_CTRL_REG 0x0422 +#define RTL8366S_LED_2_3_CTRL_REG 0x0423 + +#define RTL8366S_MIB_COUNT 33 +#define RTL8366S_GLOBAL_MIB_COUNT 1 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 +#define RTL8366S_MIB_COUNTER_BASE 0x1000 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 +#define RTL8366S_MIB_COUNTER_BASE2 0x1180 +#define RTL8366S_MIB_CTRL_REG 0x11F0 +#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF +#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 +#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 + +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC + + +#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 +#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B +#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366S_VLAN_TB_CTRL_REG 0x010F + +#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) + +#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 + +#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 +#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366S_PORT_NUM_CPU 5 +#define RTL8366S_NUM_PORTS 6 +#define RTL8366S_NUM_VLANS 16 +#define RTL8366S_NUM_LEDGROUPS 4 +#define RTL8366S_NUM_VIDS 4096 +#define RTL8366S_PRIORITYMAX 7 +#define RTL8366S_FIDMAX 7 + + +#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ + +#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ +#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN) + +#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4) + +#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_VLAN_VID_MASK 0xfff +#define RTL8366S_VLAN_PRIORITY_SHIFT 12 +#define RTL8366S_VLAN_PRIORITY_MASK 0x7 +#define RTL8366S_VLAN_MEMBER_MASK 0x3f +#define RTL8366S_VLAN_UNTAG_SHIFT 6 +#define RTL8366S_VLAN_UNTAG_MASK 0x3f +#define RTL8366S_VLAN_FID_SHIFT 12 +#define RTL8366S_VLAN_FID_MASK 0x7 + +#define RTL8366S_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366S_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + + /* + * The following counters are accessible at a different + * base address. + */ + { 1, 0, 2, "Dot1dTpPortInDiscards" }, + { 1, 2, 2, "IfOutUcastPkts" }, + { 1, 4, 2, "IfOutMulticastPkts" }, + { 1, 6, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366s_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, + RTL8366S_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + u32 phyData; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + if (enable) + phyData |= RTL8366S_PHY_POWER_SAVING_MASK; + else + phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK; + + err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData); + if (err) + return err; + + return 0; +} + +static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable) +{ + int err; + unsigned i; + u32 data = 0; + + if (!enable) { + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + rtl8366s_set_green_port(smi, i, 0); + } + } + + if (enable) + data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT); + + REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data); + + return 0; +} + +static int rtl8366s_setup(struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata; + int err; + unsigned i; +#ifdef CONFIG_OF + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; +#endif + + pdata = smi->parent->platform_data; + if (pdata && pdata->num_initvals && pdata->initvals) { + dev_info(smi->parent, "applying initvals\n"); + for (i = 0; i < pdata->num_initvals; i++) + REG_WR(smi, pdata->initvals[i].reg, + pdata->initvals[i].val); + } + +#ifdef CONFIG_OF + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } + + if (of_property_read_bool(np, "realtek,green-ethernet-features")) { + dev_info(smi->parent, "activating Green Ethernet features\n"); + + err = rtl8366s_set_green(smi, 1); + if (err) + return err; + + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + err = rtl8366s_set_green_port(smi, i, 1); + if (err) + return err; + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, + RTL8366S_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) + return -EINVAL; + + switch (rtl8366s_mib_counters[counter].base) { + case 0: + addr = RTL8366S_MIB_COUNTER_BASE + + RTL8366S_MIB_COUNTER_PORT_OFFSET * port; + break; + + case 1: + addr = RTL8366S_MIB_COUNTER_BASE2 + + RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; + break; + + default: + return -EINVAL; + } + + addr += rtl8366s_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366S_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366S_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366S_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, + vid & RTL8366S_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + if (vlan4k->vid >= RTL8366S_NUM_VIDS || + vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & + RTL8366S_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + if (index >= RTL8366S_NUM_VLANS || + vlanmc->vid >= RTL8366S_NUM_VIDS || + vlanmc->priority > RTL8366S_PRIORITYMAX || + vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << + RTL8366S_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366S_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + RTL8366S_PORT_VLAN_CTRL_MASK << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366S_PORT_VLAN_CTRL_MASK) << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, + (enable) ? RTL8366S_SGCR_EN_VLAN : 0); +} + +static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, + 1, (enable) ? 1 : 0); +} + +static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366S_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366S_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); +} + +static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, + RTL8366S_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366s_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); + + val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); + + return 0; +} + +static int rtl8366s_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char length_code; + + switch (val->value.i) { + case 0: + length_code = RTL8366S_SGCR_MAX_LENGTH_1522; + break; + case 1: + length_code = RTL8366S_SGCR_MAX_LENGTH_1536; + break; + case 2: + length_code = RTL8366S_SGCR_MAX_LENGTH_1552; + break; + case 3: + length_code = RTL8366S_SGCR_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, + RTL8366S_SGCR_MAX_LENGTH_MASK, + length_code); +} + +static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366S_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, portmask); + + return 0; +} + +static int rtl8366s_sw_get_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + int err; + + err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data); + if (err) + return err; + + val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366s_set_green(smi, val->value.i); +} + +static int rtl8366s_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366s_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366S_NUM_PORTS || + (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) + return -EINVAL; + + if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { + reg = RTL8366S_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366S_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366s_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366s_sw_get_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + u32 phyData; + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i); +} + +static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, + 0, (1 << (val->port_vlan + 3))); +} + +static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366s_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366s_sw_set_learning_enable, + .get = rtl8366s_sw_get_learning_enable, + .max = 1, + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366s_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366s_sw_set_blinkrate, + .get = rtl8366s_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", + .set = rtl8366s_sw_set_max_length, + .get = rtl8366s_sw_get_max_length, + .max = 3, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_mode", + .description = "Get/Set the router green feature", + .set = rtl8366s_sw_set_green, + .get = rtl8366s_sw_get_green, + .max = 1, + }, +}; + +static struct switch_attr rtl8366s_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366s_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366s_sw_set_port_led, + .get = rtl8366s_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_port", + .description = "Get/Set port green feature (0 - 1)", + .max = 1, + .set = rtl8366s_sw_set_green_port, + .get = rtl8366s_sw_get_green_port, + }, +}; + +static struct switch_attr rtl8366s_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366S_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366s_globals, + .n_attr = ARRAY_SIZE(rtl8366s_globals), + }, + .attr_port = { + .attr = rtl8366s_port, + .n_attr = ARRAY_SIZE(rtl8366s_port), + }, + .attr_vlan = { + .attr = rtl8366s_vlan, + .n_attr = ARRAY_SIZE(rtl8366s_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366s_sw_get_port_link, + .get_port_stats = rtl8366s_sw_get_port_stats, +}; + +static int rtl8366s_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366S"; + dev->cpu_port = RTL8366S_PORT_NUM_CPU; + dev->ports = RTL8366S_NUM_PORTS; + dev->vlans = RTL8366S_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366s_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366s_smi_ops = { + .detect = rtl8366s_detect, + .reset_chip = rtl8366s_reset_chip, + .setup = rtl8366s_setup, + + .mii_read = rtl8366s_mii_read, + .mii_write = rtl8366s_mii_write, + + .get_vlan_mc = rtl8366s_get_vlan_mc, + .set_vlan_mc = rtl8366s_set_vlan_mc, + .get_vlan_4k = rtl8366s_get_vlan_4k, + .set_vlan_4k = rtl8366s_set_vlan_4k, + .get_mc_index = rtl8366s_get_mc_index, + .set_mc_index = rtl8366s_set_mc_index, + .get_mib_counter = rtl8366_get_mib_counter, + .is_vlan_valid = rtl8366s_is_vlan_valid, + .enable_vlan = rtl8366s_enable_vlan, + .enable_vlan4k = rtl8366s_enable_vlan4k, + .enable_port = rtl8366s_enable_port, +}; + +static int rtl8366s_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366S_DRIVER_DESC + " version " RTL8366S_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366s_smi_ops; + smi->cpu_port = RTL8366S_PORT_NUM_CPU; + smi->num_ports = RTL8366S_NUM_PORTS; + smi->num_vlan_mc = RTL8366S_NUM_VLANS; + smi->mib_counters = rtl8366s_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366s_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366s_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366s_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366s_match[] = { + { .compatible = "realtek,rtl8366s" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366s_match); +#endif + +static struct platform_driver rtl8366s_driver = { + .driver = { + .name = RTL8366S_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8366s_match), +#endif + }, + .probe = rtl8366s_probe, + .remove = rtl8366s_remove, +}; + +static int __init rtl8366s_module_init(void) +{ + return platform_driver_register(&rtl8366s_driver); +} +module_init(rtl8366s_module_init); + +static void __exit rtl8366s_module_exit(void) +{ + platform_driver_unregister(&rtl8366s_driver); +} +module_exit(rtl8366s_module_exit); + +MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); +MODULE_VERSION(RTL8366S_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8367.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8367.c new file mode 100644 index 0000000000..9549961d76 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8367.c @@ -0,0 +1,1846 @@ +/* + * Platform driver for the Realtek RTL8367R/M ethernet switches + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367_PHY_ADDR_MAX 8 +#define RTL8367_PHY_REG_MAX 31 + +#define RTL8367_VID_MASK 0xffff +#define RTL8367_FID_MASK 0xfff +#define RTL8367_UNTAG_MASK 0xffff +#define RTL8367_MEMBER_MASK 0xffff + +#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 +#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367_TA_CTRL_REG 0x0500 +#define RTL8367_TA_CTRL_STATUS BIT(12) +#define RTL8367_TA_CTRL_METHOD BIT(5) +#define RTL8367_TA_CTRL_CMD_SHIFT 4 +#define RTL8367_TA_CTRL_CMD_READ 0 +#define RTL8367_TA_CTRL_CMD_WRITE 1 +#define RTL8367_TA_CTRL_TABLE_SHIFT 0 +#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367_TA_CTRL_TABLE_L2 4 +#define RTL8367_TA_CTRL_CVLAN_READ \ + ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) +#define RTL8367_TA_CTRL_CVLAN_WRITE \ + ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) + +#define RTL8367_TA_ADDR_REG 0x0501 +#define RTL8367_TA_ADDR_MASK 0x3fff + +#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) +#define RTL8367_TA_VLAN_DATA_SIZE 4 +#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK +#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 +#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_TA_VLAN_FID_SHIFT 0 +#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK +#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 +#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 +#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 +#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff + +#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) +#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f +#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) + +#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) +#define RTL8367_VLAN_MC_DATA_SIZE 4 +#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 +#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_VLAN_MC_FID_SHIFT 0 +#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK +#define RTL8367_VLAN_MC_EVID_SHIFT 0 +#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK + +#define RTL8367_VLAN_CTRL_REG 0x07a8 +#define RTL8367_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367_VLAN_INGRESS_REG 0x07a9 + +#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) + +#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) + +#define RTL8367_MIB_ADDRESS_REG 0x1004 + +#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) +#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) +#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) +#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) +#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) + +#define RTL8367_MIB_COUNT 36 +#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 + +#define RTL8367_SWC0_REG 0x1200 +#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 +#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) +#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) +#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) +#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) +#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) +#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) + +#define RTL8367_CHIP_NUMBER_REG 0x1300 + +#define RTL8367_CHIP_VER_REG 0x1301 +#define RTL8367_CHIP_VER_RLVID_SHIFT 12 +#define RTL8367_CHIP_VER_RLVID_MASK 0xf +#define RTL8367_CHIP_VER_MCID_SHIFT 8 +#define RTL8367_CHIP_VER_MCID_MASK 0xf +#define RTL8367_CHIP_VER_BOID_SHIFT 4 +#define RTL8367_CHIP_VER_BOID_MASK 0xf + +#define RTL8367_CHIP_MODE_REG 0x1302 +#define RTL8367_CHIP_MODE_MASK 0x7 + +#define RTL8367_CHIP_DEBUG0_REG 0x1303 +#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367_DIS_REG 0x1305 +#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367_DIS_RGMII_MASK 0x7 + +#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367_DI_FORCE_MODE BIT(12) +#define RTL8367_DI_FORCE_NWAY BIT(7) +#define RTL8367_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367_DI_FORCE_LINK BIT(4) +#define RTL8367_DI_FORCE_DUPLEX BIT(2) +#define RTL8367_DI_FORCE_SPEED_MASK 3 +#define RTL8367_DI_FORCE_SPEED_10 0 +#define RTL8367_DI_FORCE_SPEED_100 1 +#define RTL8367_DI_FORCE_SPEED_1000 2 + +#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367_CHIP_RESET_REG 0x1322 +#define RTL8367_CHIP_RESET_SW BIT(1) +#define RTL8367_CHIP_RESET_HW BIT(0) + +#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) +#define RTL8367_PORT_STATUS_NWAY BIT(7) +#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) +#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) +#define RTL8367_PORT_STATUS_LINK BIT(4) +#define RTL8367_PORT_STATUS_DUPLEX BIT(2) +#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8367_PORT_STATUS_SPEED_10 0 +#define RTL8367_PORT_STATUS_SPEED_100 1 +#define RTL8367_PORT_STATUS_SPEED_1000 2 + +#define RTL8367_RTL_NO_REG 0x13c0 +#define RTL8367_RTL_NO_8367R 0x3670 +#define RTL8367_RTL_NO_8367M 0x3671 + +#define RTL8367_RTL_VER_REG 0x13c1 +#define RTL8367_RTL_VER_MASK 0xf + +#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 +#define RTL8367_LED_MODE_REG 0x1b02 +#define RTL8367_LED_MODE_RATE_M 0x7 +#define RTL8367_LED_MODE_RATE_S 1 + +#define RTL8367_LED_CONFIG_REG 0x1b03 +#define RTL8367_LED_CONFIG_DATA_S 12 +#define RTL8367_LED_CONFIG_DATA_M 0x3 +#define RTL8367_LED_CONFIG_SEL BIT(14) +#define RTL8367_LED_CONFIG_LED_CFG_M 0xf + +#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 +#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 +#define RTL8367_PARA_LED_IO_EN_PMASK 0xff + +#define RTL8367_IA_CTRL_REG 0x1f00 +#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) +#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) +#define RTL8367_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367_IA_STATUS_REG 0x1f01 +#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367_IA_ADDRESS_REG 0x1f02 + +#define RTL8367_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367_IA_READ_DATA_REG 0x1f04 + +#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367_CPU_PORT_NUM 9 +#define RTL8367_NUM_PORTS 10 +#define RTL8367_NUM_VLANS 32 +#define RTL8367_NUM_LEDGROUPS 4 +#define RTL8367_NUM_VIDS 4096 +#define RTL8367_PRIORITYMAX 7 +#define RTL8367_FIDMAX 7 + +#define RTL8367_PORT_0 BIT(0) +#define RTL8367_PORT_1 BIT(1) +#define RTL8367_PORT_2 BIT(2) +#define RTL8367_PORT_3 BIT(3) +#define RTL8367_PORT_4 BIT(4) +#define RTL8367_PORT_5 BIT(5) +#define RTL8367_PORT_6 BIT(6) +#define RTL8367_PORT_7 BIT(7) +#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ +#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ + +#define RTL8367_PORTS_ALL \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ + RTL8367_PORT_E0) + +#define RTL8367_PORTS_ALL_BUT_CPU \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) + +struct rtl8367_initval { + u16 reg; + u16 val; +}; + +#define RTL8367_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8367_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 2, "Dot3StatsFCSErrors" }, + { 0, 6, 2, "Dot3StatsSymbolErrors" }, + { 0, 8, 2, "Dot3InPauseFrames" }, + { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 12, 2, "EtherStatsFragments" }, + { 0, 14, 2, "EtherStatsJabbers" }, + { 0, 16, 2, "IfInUcastPkts" }, + { 0, 18, 2, "EtherStatsDropEvents" }, + { 0, 20, 4, "EtherStatsOctets" }, + + { 0, 24, 2, "EtherStatsUnderSizePkts" }, + { 0, 26, 2, "EtherOversizeStats" }, + { 0, 28, 2, "EtherStatsPkts64Octets" }, + { 0, 30, 2, "EtherStatsPkts65to127Octets" }, + { 0, 32, 2, "EtherStatsPkts128to255Octets" }, + { 0, 34, 2, "EtherStatsPkts256to511Octets" }, + { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 40, 2, "EtherStatsMulticastPkts" }, + { 0, 42, 2, "EtherStatsBroadcastPkts" }, + + { 0, 44, 4, "IfOutOctets" }, + + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, + { 0, 72, 2, "OutOampduPkts" }, + { 0, 74, 2, "InOampduPkts" }, + { 0, 76, 2, "PktgenPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367_initval rtl8367_initvals_0_0[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, + {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, + {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, + {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, + {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, + {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, + {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, + {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, + {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, + {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, +}; + +static const struct rtl8367_initval rtl8367_initvals_0_1[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, + {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, + {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, + {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, + {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, + {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, + {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, + {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, + {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, + {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_0[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, + {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, + {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, + {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, + {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, + {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, + {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, + {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, + {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, + {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_1[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, + {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, + {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, + {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, + {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, + {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, + {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, + {0x1B03, 0x0876}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_0[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, + {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, + {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, + {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, + {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, + {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, + {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, + {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, + {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, + {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, + {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, + {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, + {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_1[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, + {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, + {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, + {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, + {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, + {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x130c, 0x0050}, +}; + +static int rtl8367_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + int err; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_0_0; + count = ARRAY_SIZE(rtl8367_initvals_0_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_0_1; + count = ARRAY_SIZE(rtl8367_initvals_0_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + err = rtl8367_write_initvals(smi, initvals, count); + if (err) + return err; + + /* TODO: complete this */ + + return 0; +} + +static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_1_0; + count = ARRAY_SIZE(rtl8367_initvals_1_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_1_1; + count = ARRAY_SIZE(rtl8367_initvals_1_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_2_0; + count = ARRAY_SIZE(rtl8367_initvals_2_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_2_1; + count = ARRAY_SIZE(rtl8367_initvals_2_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs(struct rtl8366_smi *smi) +{ + u32 data; + u32 rlvid; + u32 mode; + int err; + + REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); + + REG_RD(smi, RTL8367_CHIP_VER_REG, &data); + rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & + RTL8367_CHIP_VER_RLVID_MASK; + + REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); + mode = data & RTL8367_CHIP_MODE_MASK; + + switch (rlvid) { + case 0: + err = rtl8367_init_regs0(smi, mode); + break; + + case 1: + err = rtl8367_write_phy_reg(smi, 0, 31, 5); + if (err) + break; + + err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); + if (err) + break; + + err = rtl8367_read_phy_reg(smi, 0, 6, &data); + if (err) + break; + + if (data == 0x94eb) { + err = rtl8367_init_regs1(smi, mode); + } else if (data == 0x2104) { + err = rtl8367_init_regs2(smi, mode); + } else { + dev_err(smi->parent, "unknow phy data %04x\n", data); + return -ENODEV; + } + + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + err = -ENODEV; + break; + } + + return err; +} + +static int rtl8367_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); + msleep(RTL8367_RESET_DELAY); + + do { + REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); + if (!(data & RTL8367_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, + RTL8367_CHIP_DEBUG0_DUMMY0(id), + RTL8367_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367_DIS_REG, + RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), + mode << RTL8367_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_DI_FORCE_MODE | + RTL8367_DI_FORCE_NWAY | + RTL8367_DI_FORCE_TXPAUSE | + RTL8367_DI_FORCE_RXPAUSE | + RTL8367_DI_FORCE_LINK | + RTL8367_DI_FORCE_DUPLEX | + RTL8367_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | + (RTL8367_EXT_RGMXF_TXDELAY_MASK << + RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, + unsigned int group, u16 port_mask) +{ + u32 reg; + u32 s; + int err; + + port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; + s = (group % 2) * 8; + reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); + + REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); + + return 0; +} + +static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, + unsigned int mode) +{ + u16 mask; + u16 set; + int err; + + mode &= RTL8367_LED_CONFIG_DATA_M; + + mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | + RTL8367_LED_CONFIG_SEL; + set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + + return 0; +} + +static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, + unsigned int led, unsigned int cfg) +{ + u16 mask; + u16 set; + int err; + + mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | + RTL8367_LED_CONFIG_SEL; + set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + return 0; +} + +static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) +{ + int err; + + REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); + return 0; +} + +static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) +{ + u16 mask; + u16 set; + int err; + + mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; + set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; + REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, + RTL8367_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367_PORT_CFG_REG(i), + RTL8367_PORT_CFG_EGRESS_MODE_MASK << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, + RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); + + /* setup LEDs */ + err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); + if (err) + return err; + + err = rtl8367_led_group_set_mode(smi, 0); + if (err) + return err; + + err = rtl8367_led_op_select_parallel(smi); + if (err) + return err; + + err = rtl8367_led_blinkrate_set(smi, 1); + if (err) + return err; + + err = rtl8367_led_group_set_config(smi, 0, 2); + if (err) + return err; + + return 0; +} + +static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) + return -EINVAL; + + mib = &rtl8367_mib_counters[counter]; + addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); + + if (data & RTL8367_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367_MIB_CTRL_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & + RTL8367_TA_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & + RTL8367_TA_VLAN_FID_MASK; + vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & + RTL8367_TA_VLAN_UNTAG1_MASK; + vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & + RTL8367_TA_VLAN_UNTAG2_MASK) << 2; + + return 0; +} + +static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + if (vlan4k->vid >= RTL8367_NUM_VIDS || + vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8367_UNTAG_MASK || + vlan4k->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << + RTL8367_TA_VLAN_MEMBER_SHIFT; + data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << + RTL8367_TA_VLAN_FID_SHIFT; + data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << + RTL8367_TA_VLAN_UNTAG1_SHIFT; + data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << + RTL8367_TA_VLAN_UNTAG2_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, + vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & + RTL8367_VLAN_MC_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & + RTL8367_VLAN_MC_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & + RTL8367_VLAN_MC_EVID_MASK; + + return 0; +} + +static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + if (index >= RTL8367_NUM_VLANS || + vlanmc->vid >= RTL8367_NUM_VIDS || + vlanmc->priority > RTL8367_PRIORITYMAX || + vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || + vlanmc->untag > RTL8367_UNTAG_MASK || + vlanmc->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << + RTL8367_VLAN_MC_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << + RTL8367_VLAN_MC_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << + RTL8367_VLAN_MC_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), + RTL8367_VLAN_PVID_CTRL_MASK << + RTL8367_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367_VLAN_PVID_CTRL_MASK) << + RTL8367_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, + RTL8367_VLAN_CTRL_ENABLE, + (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), + (enable) ? RTL8367_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, + RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); +} + +static int rtl8367_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); + + speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); + val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> + RTL8367_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, + RTL8367_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, + RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367_sw_set_max_length, + .get = rtl8367_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8367_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8367_sw_ops = { + .attr_global = { + .attr = rtl8367_globals, + .n_attr = ARRAY_SIZE(rtl8367_globals), + }, + .attr_port = { + .attr = rtl8367_port, + .n_attr = ARRAY_SIZE(rtl8367_port), + }, + .attr_vlan = { + .attr = rtl8367_vlan, + .n_attr = ARRAY_SIZE(rtl8367_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367_sw_get_port_link, + .get_port_stats = rtl8367_sw_get_port_stats, +}; + +static int rtl8367_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367"; + dev->cpu_port = RTL8367_CPU_PORT_NUM; + dev->ports = RTL8367_NUM_PORTS; + dev->vlans = RTL8367_NUM_VIDS; + dev->ops = &rtl8367_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367_detect(struct rtl8366_smi *smi) +{ + u32 rtl_no = 0; + u32 rtl_ver = 0; + char *chip_name; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); + if (ret) { + dev_err(smi->parent, "unable to read chip number\n"); + return ret; + } + + switch (rtl_no) { + case RTL8367_RTL_NO_8367R: + chip_name = "8367R"; + break; + case RTL8367_RTL_NO_8367M: + chip_name = "8367M"; + break; + default: + dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%s ver. %u chip found\n", + chip_name, rtl_ver & RTL8367_RTL_VER_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367_smi_ops = { + .detect = rtl8367_detect, + .reset_chip = rtl8367_reset_chip, + .setup = rtl8367_setup, + + .mii_read = rtl8367_mii_read, + .mii_write = rtl8367_mii_write, + + .get_vlan_mc = rtl8367_get_vlan_mc, + .set_vlan_mc = rtl8367_set_vlan_mc, + .get_vlan_4k = rtl8367_get_vlan_4k, + .set_vlan_4k = rtl8367_set_vlan_4k, + .get_mc_index = rtl8367_get_mc_index, + .set_mc_index = rtl8367_set_mc_index, + .get_mib_counter = rtl8367_get_mib_counter, + .is_vlan_valid = rtl8367_is_vlan_valid, + .enable_vlan = rtl8367_enable_vlan, + .enable_vlan4k = rtl8367_enable_vlan4k, + .enable_port = rtl8367_enable_port, +}; + +static int rtl8367_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367_smi_ops; + smi->cpu_port = RTL8367_CPU_PORT_NUM; + smi->num_ports = RTL8367_NUM_PORTS; + smi->num_vlan_mc = RTL8367_NUM_VLANS; + smi->mib_counters = rtl8367_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367_match[] = { + { .compatible = "realtek,rtl8367" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367_match); +#endif + +static struct platform_driver rtl8367_driver = { + .driver = { + .name = RTL8367_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367_match), +#endif + }, + .probe = rtl8367_probe, + .remove = rtl8367_remove, + .shutdown = rtl8367_shutdown, +}; + +static int __init rtl8367_module_init(void) +{ + return platform_driver_register(&rtl8367_driver); +} +module_init(rtl8367_module_init); + +static void __exit rtl8367_module_exit(void) +{ + platform_driver_unregister(&rtl8367_driver); +} +module_exit(rtl8367_module_exit); + +MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/rtl8367b.c b/target/linux/generic/files-3.18/drivers/net/phy/rtl8367b.c new file mode 100644 index 0000000000..e6ea6509ae --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/rtl8367b.c @@ -0,0 +1,1612 @@ +/* + * Platform driver for the Realtek RTL8367R-VB ethernet switches + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367B_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367B_PHY_ADDR_MAX 8 +#define RTL8367B_PHY_REG_MAX 31 + +#define RTL8367B_VID_MASK 0x3fff +#define RTL8367B_FID_MASK 0xf +#define RTL8367B_UNTAG_MASK 0xff +#define RTL8367B_MEMBER_MASK 0xff + +#define RTL8367B_PORT_MISC_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI 2 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367B_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367B_TA_CTRL_REG 0x0500 /*GOOD*/ +#define RTL8367B_TA_CTRL_SPA_SHIFT 8 +#define RTL8367B_TA_CTRL_SPA_MASK 0x7 +#define RTL8367B_TA_CTRL_METHOD BIT(4)/*GOOD*/ +#define RTL8367B_TA_CTRL_CMD_SHIFT 3 +#define RTL8367B_TA_CTRL_CMD_READ 0 +#define RTL8367B_TA_CTRL_CMD_WRITE 1 +#define RTL8367B_TA_CTRL_TABLE_SHIFT 0 /*GOOD*/ +#define RTL8367B_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367B_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367B_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367B_TA_CTRL_TABLE_L2 4 +#define RTL8367B_TA_CTRL_CVLAN_READ \ + ((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) +#define RTL8367B_TA_CTRL_CVLAN_WRITE \ + ((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) + +#define RTL8367B_TA_ADDR_REG 0x0501/*GOOD*/ +#define RTL8367B_TA_ADDR_MASK 0x3fff/*GOOD*/ + +#define RTL8367B_TA_LUT_REG 0x0502/*GOOD*/ + +#define RTL8367B_TA_WRDATA_REG(_x) (0x0510 + (_x))/*GOOD*/ +#define RTL8367B_TA_VLAN_NUM_WORDS 2 +#define RTL8367B_TA_VLAN_VID_MASK RTL8367B_VID_MASK +#define RTL8367B_TA_VLAN0_MEMBER_SHIFT 0 +#define RTL8367B_TA_VLAN0_MEMBER_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN0_UNTAG_SHIFT 8 +#define RTL8367B_TA_VLAN0_UNTAG_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN1_FID_SHIFT 0 +#define RTL8367B_TA_VLAN1_FID_MASK RTL8367B_FID_MASK + +#define RTL8367B_TA_RDDATA_REG(_x) (0x0520 + (_x))/*GOOD*/ + +#define RTL8367B_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_MASK 0x1f /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) /*GOOD*/ + +#define RTL8367B_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) /*GOOD*/ +#define RTL8367B_VLAN_MC_NUM_WORDS 4 /*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_MASK RTL8367B_MEMBER_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_MASK RTL8367B_FID_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_MASK RTL8367B_VID_MASK/*GOOD*/ + +#define RTL8367B_VLAN_CTRL_REG 0x07a8 /*GOOD*/ +#define RTL8367B_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367B_VLAN_INGRESS_REG 0x07a9 /*GOOD*/ + +#define RTL8367B_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) /*GOOD*/ + +#define RTL8367B_MIB_COUNTER_REG(_x) (0x1000 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_COUNTER_PORT_OFFSET 0x007c /*GOOD*/ + +#define RTL8367B_MIB_ADDRESS_REG 0x1004 /*GOOD*/ + +#define RTL8367B_MIB_CTRL0_REG(_x) (0x1005 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK BIT(11) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_QM_RESET_MASK BIT(10) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_RESET_MASK BIT(1) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_BUSY_MASK BIT(0) /*GOOD*/ + +#define RTL8367B_SWC0_REG 0x1200/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_SHIFT 13/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH(_x) ((_x) << 13) /*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_MASK RTL8367B_SWC0_MAX_LENGTH(0x3) +#define RTL8367B_SWC0_MAX_LENGTH_1522 RTL8367B_SWC0_MAX_LENGTH(0) +#define RTL8367B_SWC0_MAX_LENGTH_1536 RTL8367B_SWC0_MAX_LENGTH(1) +#define RTL8367B_SWC0_MAX_LENGTH_1552 RTL8367B_SWC0_MAX_LENGTH(2) +#define RTL8367B_SWC0_MAX_LENGTH_16000 RTL8367B_SWC0_MAX_LENGTH(3) + +#define RTL8367B_CHIP_NUMBER_REG 0x1300/*GOOD*/ + +#define RTL8367B_CHIP_VER_REG 0x1301/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_SHIFT 12/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_SHIFT 8/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_SHIFT 4/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_SHIFT 0/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_MASK 0x1/*GOOD*/ + +#define RTL8367B_CHIP_MODE_REG 0x1302 +#define RTL8367B_CHIP_MODE_MASK 0x7 + +#define RTL8367B_CHIP_DEBUG0_REG 0x1303 +#define RTL8367B_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367B_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367B_DIS_REG 0x1305 +#define RTL8367B_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367B_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367B_DIS_RGMII_MASK 0x7 + +#define RTL8367B_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367B_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367B_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367B_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367B_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367B_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367B_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367B_DI_FORCE_MODE BIT(12) +#define RTL8367B_DI_FORCE_NWAY BIT(7) +#define RTL8367B_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367B_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367B_DI_FORCE_LINK BIT(4) +#define RTL8367B_DI_FORCE_DUPLEX BIT(2) +#define RTL8367B_DI_FORCE_SPEED_MASK 3 +#define RTL8367B_DI_FORCE_SPEED_10 0 +#define RTL8367B_DI_FORCE_SPEED_100 1 +#define RTL8367B_DI_FORCE_SPEED_1000 2 + +#define RTL8367B_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367B_CHIP_RESET_REG 0x1322 /*GOOD*/ +#define RTL8367B_CHIP_RESET_SW BIT(1) /*GOOD*/ +#define RTL8367B_CHIP_RESET_HW BIT(0) /*GOOD*/ + +#define RTL8367B_PORT_STATUS_REG(_p) (0x1352 + (_p)) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_1000_SPI BIT(11) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_100_SPI BIT(10)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY_FAULT BIT(9)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK_MASTER BIT(8)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY BIT(7)/*GOOD*/ +#define RTL8367B_PORT_STATUS_TXPAUSE BIT(6)/*GOOD*/ +#define RTL8367B_PORT_STATUS_RXPAUSE BIT(5)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK BIT(4)/*GOOD*/ +#define RTL8367B_PORT_STATUS_DUPLEX BIT(2)/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_MASK 0x0003/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_10 0/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_100 1/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_1000 2/*GOOD*/ + +#define RTL8367B_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367B_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367B_IA_CTRL_REG 0x1f00 +#define RTL8367B_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367B_IA_CTRL_RW_READ RTL8367B_IA_CTRL_RW(0) +#define RTL8367B_IA_CTRL_RW_WRITE RTL8367B_IA_CTRL_RW(1) +#define RTL8367B_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367B_IA_STATUS_REG 0x1f01 +#define RTL8367B_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367B_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367B_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367B_IA_ADDRESS_REG 0x1f02 +#define RTL8367B_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367B_IA_READ_DATA_REG 0x1f04 + +#define RTL8367B_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367B_NUM_MIB_COUNTERS 58 + +#define RTL8367B_CPU_PORT_NUM 5 +#define RTL8367B_NUM_PORTS 8 +#define RTL8367B_NUM_VLANS 32 +#define RTL8367B_NUM_VIDS 4096 +#define RTL8367B_PRIORITYMAX 7 +#define RTL8367B_FIDMAX 7 + +#define RTL8367B_PORT_0 BIT(0) +#define RTL8367B_PORT_1 BIT(1) +#define RTL8367B_PORT_2 BIT(2) +#define RTL8367B_PORT_3 BIT(3) +#define RTL8367B_PORT_4 BIT(4) +#define RTL8367B_PORT_E0 BIT(5) /* External port 0 */ +#define RTL8367B_PORT_E1 BIT(6) /* External port 1 */ +#define RTL8367B_PORT_E2 BIT(7) /* External port 2 */ + +#define RTL8367B_PORTS_ALL \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \ + RTL8367B_PORT_E1 | RTL8367B_PORT_E2) + +#define RTL8367B_PORTS_ALL_BUT_CPU \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 | \ + RTL8367B_PORT_E2) + +struct rtl8367b_initval { + u16 reg; + u16 val; +}; + +#define RTL8367B_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367B_MIB_TXB_ID 28 /* IfOutOctets */ + +static struct rtl8366_mib_counter +rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = { + {0, 0, 4, "ifInOctets" }, + {0, 4, 2, "dot3StatsFCSErrors" }, + {0, 6, 2, "dot3StatsSymbolErrors" }, + {0, 8, 2, "dot3InPauseFrames" }, + {0, 10, 2, "dot3ControlInUnknownOpcodes" }, + {0, 12, 2, "etherStatsFragments" }, + {0, 14, 2, "etherStatsJabbers" }, + {0, 16, 2, "ifInUcastPkts" }, + {0, 18, 2, "etherStatsDropEvents" }, + {0, 20, 2, "ifInMulticastPkts" }, + {0, 22, 2, "ifInBroadcastPkts" }, + {0, 24, 2, "inMldChecksumError" }, + {0, 26, 2, "inIgmpChecksumError" }, + {0, 28, 2, "inMldSpecificQuery" }, + {0, 30, 2, "inMldGeneralQuery" }, + {0, 32, 2, "inIgmpSpecificQuery" }, + {0, 34, 2, "inIgmpGeneralQuery" }, + {0, 36, 2, "inMldLeaves" }, + {0, 38, 2, "inIgmpLeaves" }, + + {0, 40, 4, "etherStatsOctets" }, + {0, 44, 2, "etherStatsUnderSizePkts" }, + {0, 46, 2, "etherOversizeStats" }, + {0, 48, 2, "etherStatsPkts64Octets" }, + {0, 50, 2, "etherStatsPkts65to127Octets" }, + {0, 52, 2, "etherStatsPkts128to255Octets" }, + {0, 54, 2, "etherStatsPkts256to511Octets" }, + {0, 56, 2, "etherStatsPkts512to1023Octets" }, + {0, 58, 2, "etherStatsPkts1024to1518Octets" }, + + {0, 60, 4, "ifOutOctets" }, + {0, 64, 2, "dot3StatsSingleCollisionFrames" }, + {0, 66, 2, "dot3StatMultipleCollisionFrames" }, + {0, 68, 2, "dot3sDeferredTransmissions" }, + {0, 70, 2, "dot3StatsLateCollisions" }, + {0, 72, 2, "etherStatsCollisions" }, + {0, 74, 2, "dot3StatsExcessiveCollisions" }, + {0, 76, 2, "dot3OutPauseFrames" }, + {0, 78, 2, "ifOutDiscards" }, + {0, 80, 2, "dot1dTpPortInDiscards" }, + {0, 82, 2, "ifOutUcastPkts" }, + {0, 84, 2, "ifOutMulticastPkts" }, + {0, 86, 2, "ifOutBroadcastPkts" }, + {0, 88, 2, "outOampduPkts" }, + {0, 90, 2, "inOampduPkts" }, + {0, 92, 2, "inIgmpJoinsSuccess" }, + {0, 94, 2, "inIgmpJoinsFail" }, + {0, 96, 2, "inMldJoinsSuccess" }, + {0, 98, 2, "inMldJoinsFail" }, + {0, 100, 2, "inReportSuppressionDrop" }, + {0, 102, 2, "inLeaveSuppressionDrop" }, + {0, 104, 2, "outIgmpReports" }, + {0, 106, 2, "outIgmpLeaves" }, + {0, 108, 2, "outIgmpGeneralQuery" }, + {0, 110, 2, "outIgmpSpecificQuery" }, + {0, 112, 2, "outMldReports" }, + {0, 114, 2, "outMldLeaves" }, + {0, 116, 2, "outMldGeneralQuery" }, + {0, 118, 2, "outMldSpecificQuery" }, + {0, 120, 2, "inKnownMulticastPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14}, + {0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002}, + {0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000}, + {0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000}, + {0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000}, + {0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013}, + {0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, + {0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02}, + {0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007}, + {0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044}, + {0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1}, + {0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026}, + {0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000}, + {0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E}, + {0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00}, + {0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01}, + {0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02}, + {0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03}, + {0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04}, + {0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05}, + {0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06}, + {0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00}, + {0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000}, + {0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015}, + {0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E}, + {0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000}, + {0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E}, + {0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1}, + {0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E}, + {0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0}, + {0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0}, + {0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0}, + {0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0}, + {0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0}, + {0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0}, + {0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0}, + {0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2}, + {0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4}, + {0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4}, + {0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA}, + {0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA}, + {0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85}, + {0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B}, + {0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A}, + {0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A}, + {0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B}, + {0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF}, + {0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8}, + {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620}, + {0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225}, + {0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302}, + {0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1}, + {0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282}, + {0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6}, + {0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4}, + {0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305}, + {0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E}, + {0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E}, + {0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25}, + {0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B}, + {0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B}, + {0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4}, + {0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23}, + {0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359}, + {0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC}, + {0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29}, + {0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1}, + {0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29}, + {0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE}, + {0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0}, + {0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, + {0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0}, + {0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84}, + {0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72}, + {0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC}, + {0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, + {0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1}, + {0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B}, + {0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7}, + {0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC}, + {0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602}, + {0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02}, + {0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602}, + {0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE}, + {0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD}, + {0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22}, + {0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC}, + {0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F}, + {0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13}, + {0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87}, + {0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0}, + {0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0}, + {0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4}, + {0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12}, + {0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12}, + {0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146}, + {0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0}, + {0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F}, + {0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21}, + {0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF}, + {0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5}, + {0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10}, + {0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459}, + {0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F}, + {0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96}, + {0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF}, + {0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0}, + {0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F}, + {0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159}, + {0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C}, + {0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302}, + {0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34}, + {0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0}, + {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D}, + {0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A}, + {0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34}, + {0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34}, + {0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0}, + {0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D}, + {0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF}, + {0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498}, + {0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03}, + {0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207}, + {0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD}, + {0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31}, + {0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31}, + {0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284}, + {0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02}, + {0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4}, + {0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0}, + {0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1}, + {0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1}, + {0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, + {0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A}, + {0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0}, + {0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A}, + {0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C}, + {0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02}, + {0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA}, + {0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11}, + {0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04}, + {0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE}, + {0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE}, + {0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE}, + {0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34}, + {0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, + {0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, + {0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0}, + {0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20}, + {0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21}, + {0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE}, + {0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284}, + {0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8}, + {0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402}, + {0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285}, + {0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B}, + {0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D}, + {0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B}, + {0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8}, + {0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1}, + {0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5}, + {0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021}, + {0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000}, + {0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212}, + {0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26}, + {0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8}, + {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22}, + {0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464}, + {0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480}, + {0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142}, + {0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000}, + {0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010}, + {0x13EB, 0x11BB} +}; + +static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA}, + {0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078}, + {0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00}, + {0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000}, + {0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000}, + {0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000}, + {0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B}, + {0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00}, + {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100}, + {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0}, + {0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1}, + {0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE}, + {0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1}, + {0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD}, + {0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20}, + {0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A}, + {0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E}, + {0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5}, + {0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2}, + {0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC}, + {0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE}, + {0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, + {0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685}, + {0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85}, + {0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC}, + {0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140}, + {0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E}, + {0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22}, + {0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340}, + {0x133E, 0x000E}, {0x133F, 0x0010}, +}; + +static int rtl8367b_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367b_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367b_init_regs(struct rtl8366_smi *smi) +{ + const struct rtl8367b_initval *initvals; + u32 chip_ver; + u32 rlvid; + int count; + int err; + + REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL); + REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + + rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) & + RTL8367B_CHIP_VER_RLVID_MASK; + + switch (rlvid) { + case 0: + initvals = rtl8367r_vb_initvals_0; + count = ARRAY_SIZE(rtl8367r_vb_initvals_0); + break; + + case 1: + initvals = rtl8367r_vb_initvals_1; + count = ARRAY_SIZE(rtl8367r_vb_initvals_1); + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + return -ENODEV; + } + + /* TODO: disable RLTP */ + + return rtl8367b_write_initvals(smi, initvals, count); +} + +static int rtl8367b_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW); + msleep(RTL8367B_RESET_DELAY); + + do { + REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data); + if (!(data & RTL8367B_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG, + RTL8367B_CHIP_DEBUG0_DUMMY0(id), + RTL8367B_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367B_DIS_REG, + RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id), + mode << RTL8367B_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_DI_FORCE_MODE | + RTL8367B_DI_FORCE_NWAY | + RTL8367B_DI_FORCE_TXPAUSE | + RTL8367B_DI_FORCE_RXPAUSE | + RTL8367B_DI_FORCE_LINK | + RTL8367B_DI_FORCE_DUPLEX | + RTL8367B_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK | + (RTL8367B_EXT_RGMXF_TXDELAY_MASK << + RTL8367B_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367b_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367b_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367b_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367b_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367b_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367b_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK, + RTL8367B_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367B_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367B_PORT_MISC_CFG_REG(i), + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT, + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT); + + return 0; +} + +static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367B_NUM_PORTS || + counter >= RTL8367B_NUM_MIB_COUNTERS) + return -EINVAL; + + mib = &rtl8367b_mib_counters[counter]; + addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data); + + if (data & RTL8367B_MIB_CTRL0_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367B_MIB_CTRL0_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367B_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) & + RTL8367B_TA_VLAN0_MEMBER_MASK; + vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) & + RTL8367B_TA_VLAN0_UNTAG_MASK; + vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) & + RTL8367B_TA_VLAN1_FID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + if (vlan4k->vid >= RTL8367B_NUM_VIDS || + vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK || + vlan4k->untag > RTL8367B_UNTAG_MASK || + vlan4k->fid > RTL8367B_FIDMAX) + return -EINVAL; + + memset(data, 0, sizeof(data)); + + data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) << + RTL8367B_TA_VLAN0_MEMBER_SHIFT; + data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) << + RTL8367B_TA_VLAN0_UNTAG_SHIFT; + data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) << + RTL8367B_TA_VLAN1_FID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, + vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) & + RTL8367B_VLAN_MC0_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) & + RTL8367B_VLAN_MC1_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) & + RTL8367B_VLAN_MC3_EVID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + if (index >= RTL8367B_NUM_VLANS || + vlanmc->vid >= RTL8367B_NUM_VIDS || + vlanmc->priority > RTL8367B_PRIORITYMAX || + vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK || + vlanmc->untag > RTL8367B_UNTAG_MASK || + vlanmc->fid > RTL8367B_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) << + RTL8367B_VLAN_MC0_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) << + RTL8367B_VLAN_MC1_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) << + RTL8367B_VLAN_MC3_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367B_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), + RTL8367B_VLAN_PVID_CTRL_MASK << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367B_VLAN_PVID_CTRL_MASK) << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG, + RTL8367B_VLAN_CTRL_ENABLE, + (enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367B_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367B_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port), + (enable) ? RTL8367B_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367b_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0, + RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK); +} + +static int rtl8367b_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367B_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY); + + speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367b_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data); + val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >> + RTL8367B_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367b_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367B_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367B_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367B_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367B_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG, + RTL8367B_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0, + RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367b_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367b_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367b_sw_set_max_length, + .get = rtl8367b_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367b_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367b_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367b_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, +}; + +static const struct switch_dev_ops rtl8367b_sw_ops = { + .attr_global = { + .attr = rtl8367b_globals, + .n_attr = ARRAY_SIZE(rtl8367b_globals), + }, + .attr_port = { + .attr = rtl8367b_port, + .n_attr = ARRAY_SIZE(rtl8367b_port), + }, + .attr_vlan = { + .attr = rtl8367b_vlan, + .n_attr = ARRAY_SIZE(rtl8367b_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367b_sw_get_port_link, + .get_port_stats = rtl8367b_sw_get_port_stats, +}; + +static int rtl8367b_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367B"; + dev->cpu_port = RTL8367B_CPU_PORT_NUM; + dev->ports = RTL8367B_NUM_PORTS; + dev->vlans = RTL8367B_NUM_VIDS; + dev->ops = &rtl8367b_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367b_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367b_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367b_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367b_detect(struct rtl8366_smi *smi) +{ + const char *chip_name; + u32 chip_num; + u32 chip_ver; + u32 chip_mode; + int ret; + + /* TODO: improve chip detection */ + rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG, + RTL8367B_RTL_MAGIC_ID_VAL); + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip number"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip version"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip mode"); + return ret; + } + + switch (chip_ver) { + case 0x1000: + chip_name = "8367RB"; + break; + case 0x1010: + chip_name = "8367R-VB"; + break; + default: + dev_err(smi->parent, + "unknown chip num:%04x ver:%04x, mode:%04x\n", + chip_num, chip_ver, chip_mode); + return -ENODEV; + } + + dev_info(smi->parent, "RTL%s chip found\n", chip_name); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367b_smi_ops = { + .detect = rtl8367b_detect, + .reset_chip = rtl8367b_reset_chip, + .setup = rtl8367b_setup, + + .mii_read = rtl8367b_mii_read, + .mii_write = rtl8367b_mii_write, + + .get_vlan_mc = rtl8367b_get_vlan_mc, + .set_vlan_mc = rtl8367b_set_vlan_mc, + .get_vlan_4k = rtl8367b_get_vlan_4k, + .set_vlan_4k = rtl8367b_set_vlan_4k, + .get_mc_index = rtl8367b_get_mc_index, + .set_mc_index = rtl8367b_set_mc_index, + .get_mib_counter = rtl8367b_get_mib_counter, + .is_vlan_valid = rtl8367b_is_vlan_valid, + .enable_vlan = rtl8367b_enable_vlan, + .enable_vlan4k = rtl8367b_enable_vlan4k, + .enable_port = rtl8367b_enable_port, +}; + +static int rtl8367b_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367b_smi_ops; + smi->cpu_port = RTL8367B_CPU_PORT_NUM; + smi->num_ports = RTL8367B_NUM_PORTS; + smi->num_vlan_mc = RTL8367B_NUM_VLANS; + smi->mib_counters = rtl8367b_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367b_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367b_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367b_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367b_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367b_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367b_match[] = { + { .compatible = "realtek,rtl8367b" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367b_match); +#endif + +static struct platform_driver rtl8367b_driver = { + .driver = { + .name = RTL8367B_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367b_match), +#endif + }, + .probe = rtl8367b_probe, + .remove = rtl8367b_remove, + .shutdown = rtl8367b_shutdown, +}; + +module_platform_driver(rtl8367b_driver); + +MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME); + diff --git a/target/linux/generic/files-3.18/drivers/net/phy/swconfig.c b/target/linux/generic/files-3.18/drivers/net/phy/swconfig.c new file mode 100644 index 0000000000..e8a684742c --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/swconfig.c @@ -0,0 +1,1256 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWCONFIG_DEVNAME "switch%d" + +#include "swconfig_leds.c" + +MODULE_AUTHOR("Felix Fietkau "); +MODULE_LICENSE("GPL"); + +static int swdev_id; +static struct list_head swdevs; +static DEFINE_MUTEX(swdevs_lock); +struct swconfig_callback; + +struct swconfig_callback { + struct sk_buff *msg; + struct genlmsghdr *hdr; + struct genl_info *info; + int cmd; + + /* callback for filling in the message data */ + int (*fill)(struct swconfig_callback *cb, void *arg); + + /* callback for closing the message before sending it */ + int (*close)(struct swconfig_callback *cb, void *arg); + + struct nlattr *nest[4]; + int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + int ret; + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + if (!dev->ops->get_vlan_ports) + return -EOPNOTSUPP; + + ret = dev->ops->get_vlan_ports(dev, val); + return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port *ports = val->value.ports; + const struct switch_dev_ops *ops = dev->ops; + int i; + + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + /* validate ports */ + if (val->len > dev->ports) + return -EINVAL; + + if (!ops->set_vlan_ports) + return -EOPNOTSUPP; + + for (i = 0; i < val->len; i++) { + if (ports[i].id >= dev->ports) + return -EINVAL; + + if (ops->set_port_pvid && + !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + ops->set_port_pvid(dev, ports[i].id, val->port_vlan); + } + + return ops->set_vlan_ports(dev, val); +} + +static int +swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->set_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); +} + +static int +swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); +} + +static int +swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (!dev->ops->set_port_link) + return -EOPNOTSUPP; + + return dev->ops->set_port_link(dev, val->port_vlan, val->value.link); +} + +static int +swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port_link *link = val->value.link; + + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_link) + return -EOPNOTSUPP; + + memset(link, 0, sizeof(*link)); + return dev->ops->get_port_link(dev, val->port_vlan, link); +} + +static int +swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->apply_config) + return 0; + + return dev->ops->apply_config(dev); +} + +static int +swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->reset_switch) + return 0; + + return dev->ops->reset_switch(dev); +} + +enum global_defaults { + GLOBAL_APPLY, + GLOBAL_RESET, +}; + +enum vlan_defaults { + VLAN_PORTS, +}; + +enum port_defaults { + PORT_PVID, + PORT_LINK, +}; + +static struct switch_attr default_global[] = { + [GLOBAL_APPLY] = { + .type = SWITCH_TYPE_NOVAL, + .name = "apply", + .description = "Activate changes in the hardware", + .set = swconfig_apply_config, + }, + [GLOBAL_RESET] = { + .type = SWITCH_TYPE_NOVAL, + .name = "reset", + .description = "Reset the switch", + .set = swconfig_reset_switch, + } +}; + +static struct switch_attr default_port[] = { + [PORT_PVID] = { + .type = SWITCH_TYPE_INT, + .name = "pvid", + .description = "Primary VLAN ID", + .set = swconfig_set_pvid, + .get = swconfig_get_pvid, + }, + [PORT_LINK] = { + .type = SWITCH_TYPE_LINK, + .name = "link", + .description = "Get port link information", + .set = swconfig_set_link, + .get = swconfig_get_link, + } +}; + +static struct switch_attr default_vlan[] = { + [VLAN_PORTS] = { + .type = SWITCH_TYPE_PORTS, + .name = "ports", + .description = "VLAN port mapping", + .set = swconfig_set_vlan_ports, + .get = swconfig_get_vlan_ports, + }, +}; + +static const struct switch_attr * +swconfig_find_attr_by_name(const struct switch_attrlist *alist, + const char *name) +{ + int i; + + for (i = 0; i < alist->n_attr; i++) + if (strcmp(name, alist->attr[i].name) == 0) + return &alist->attr[i]; + + return NULL; +} + +static void swconfig_defaults_init(struct switch_dev *dev) +{ + const struct switch_dev_ops *ops = dev->ops; + + dev->def_global = 0; + dev->def_vlan = 0; + dev->def_port = 0; + + if (ops->get_vlan_ports || ops->set_vlan_ports) + set_bit(VLAN_PORTS, &dev->def_vlan); + + if (ops->get_port_pvid || ops->set_port_pvid) + set_bit(PORT_PVID, &dev->def_port); + + if (ops->get_port_link && + !swconfig_find_attr_by_name(&ops->attr_port, "link")) + set_bit(PORT_LINK, &dev->def_port); + + /* always present, can be no-op */ + set_bit(GLOBAL_APPLY, &dev->def_global); + set_bit(GLOBAL_RESET, &dev->def_global); +} + + +static struct genl_family switch_fam; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { + [SWITCH_ATTR_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, + [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, + [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { + [SWITCH_PORT_ID] = { .type = NLA_U32 }, + [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = { + [SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG }, + [SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG }, + [SWITCH_LINK_SPEED] = { .type = NLA_U32 }, +}; + +static inline void +swconfig_lock(void) +{ + mutex_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ + mutex_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ + struct switch_dev *dev = NULL; + struct switch_dev *p; + int id; + + if (!info->attrs[SWITCH_ATTR_ID]) + goto done; + + id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); + swconfig_lock(); + list_for_each_entry(p, &swdevs, dev_list) { + if (id != p->id) + continue; + + dev = p; + break; + } + if (dev) + mutex_lock(&dev->sw_mutex); + else + pr_debug("device %d not found\n", id); + swconfig_unlock(); +done: + return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ + mutex_unlock(&dev->sw_mutex); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ + struct switch_attr *op = arg; + struct genl_info *info = cb->info; + struct sk_buff *msg = cb->msg; + int id = cb->args[0]; + void *hdr; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) + goto nla_put_failure; + if (op->description) + if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, + op->description)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ + struct genl_info *info = cb->info; + int restart = 0; + int err; + + do { + if (!cb->msg) { + cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (cb->msg == NULL) + goto error; + } + + if (!(cb->fill(cb, arg) < 0)) + break; + + /* fill failed, check if this was already the second attempt */ + if (restart) + goto error; + + /* try again in a new message, send the current one */ + restart = 1; + if (cb->close) { + if (cb->close(cb, arg) < 0) + goto error; + } + err = genlmsg_reply(cb->msg, info); + cb->msg = NULL; + if (err < 0) + goto error; + + } while (restart); + + return 0; + +error: + if (cb->msg) + nlmsg_free(cb->msg); + return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + struct switch_dev *dev; + struct swconfig_callback cb; + int err = -EINVAL; + int i; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + switch (hdr->cmd) { + case SWITCH_CMD_LIST_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_LIST_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + break; + case SWITCH_CMD_LIST_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + break; + default: + WARN_ON(1); + goto out; + } + + memset(&cb, 0, sizeof(cb)); + cb.info = info; + cb.fill = swconfig_dump_attr; + for (i = 0; i < alist->n_attr; i++) { + if (alist->attr[i].disabled) + continue; + cb.args[0] = i; + err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); + if (err < 0) + goto error; + } + + /* defaults */ + for (i = 0; i < n_def; i++) { + if (!test_bit(i, def_active)) + continue; + cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; + err = swconfig_send_multipart(&cb, (void *) &def_list[i]); + if (err < 0) + goto error; + } + swconfig_put_dev(dev); + + if (!cb.msg) + return 0; + + return genlmsg_reply(cb.msg, info); + +error: + if (cb.msg) + nlmsg_free(cb.msg); +out: + swconfig_put_dev(dev); + return err; +} + +static const struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, + struct switch_val *val) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + const struct switch_attr *attr = NULL; + unsigned int attr_id; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + if (!info->attrs[SWITCH_ATTR_OP_ID]) + goto done; + + switch (hdr->cmd) { + case SWITCH_CMD_SET_GLOBAL: + case SWITCH_CMD_GET_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_SET_VLAN: + case SWITCH_CMD_GET_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + if (!info->attrs[SWITCH_ATTR_OP_VLAN]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); + if (val->port_vlan >= dev->vlans) + goto done; + break; + case SWITCH_CMD_SET_PORT: + case SWITCH_CMD_GET_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + if (!info->attrs[SWITCH_ATTR_OP_PORT]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); + if (val->port_vlan >= dev->ports) + goto done; + break; + default: + WARN_ON(1); + goto done; + } + + if (!alist) + goto done; + + attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); + if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { + attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; + if (attr_id >= n_def) + goto done; + if (!test_bit(attr_id, def_active)) + goto done; + attr = &def_list[attr_id]; + } else { + if (attr_id >= alist->n_attr) + goto done; + attr = &alist->attr[attr_id]; + } + + if (attr->disabled) + attr = NULL; + +done: + if (!attr) + pr_debug("attribute lookup failed\n"); + val->attr = attr; + return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, + struct switch_val *val, int max) +{ + struct nlattr *nla; + int rem; + + val->len = 0; + nla_for_each_nested(nla, head, rem) { + struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; + struct switch_port *port; + + if (val->len >= max) + return -EINVAL; + + port = &val->value.ports[val->len]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy)) +#else + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy, NULL)) +#endif + return -EINVAL; + + if (!tb[SWITCH_PORT_ID]) + return -EINVAL; + + port->id = nla_get_u32(tb[SWITCH_PORT_ID]); + if (tb[SWITCH_PORT_FLAG_TAGGED]) + port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla, + struct switch_port_link *link) +{ + struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy)) +#else + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL)) +#endif + return -EINVAL; + + link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX]; + link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG]; + link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]); + + return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ + const struct switch_attr *attr; + struct switch_dev *dev; + struct switch_val val; + int err = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->set) + goto error; + + val.attr = attr; + switch (attr->type) { + case SWITCH_TYPE_NOVAL: + break; + case SWITCH_TYPE_INT: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) + goto error; + val.value.i = + nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); + break; + case SWITCH_TYPE_STRING: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) + goto error; + val.value.s = + nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); + break; + case SWITCH_TYPE_PORTS: + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + + /* TODO: implement multipart? */ + if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { + err = swconfig_parse_ports(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], + &val, dev->ports); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + case SWITCH_TYPE_LINK: + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + + if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) { + err = swconfig_parse_link(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_LINK], + val.value.link); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + default: + goto error; + } + + err = attr->set(dev, attr, &val); +error: + swconfig_put_dev(dev); + return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ + if (cb->nest[0]) + nla_nest_end(cb->msg, cb->nest[0]); + return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ + const struct switch_port *port = arg; + struct nlattr *p = NULL; + + if (!cb->nest[0]) { + cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); + if (!cb->nest[0]) + return -1; + } + + p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); + if (!p) + goto error; + + if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) + goto nla_put_failure; + if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) + goto nla_put_failure; + } + + nla_nest_end(cb->msg, p); + return 0; + +nla_put_failure: + nla_nest_cancel(cb->msg, p); +error: + nla_nest_cancel(cb->msg, cb->nest[0]); + return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, + const struct switch_val *val) +{ + struct swconfig_callback cb; + int err = 0; + int i; + + if (!val->value.ports) + return -EINVAL; + + memset(&cb, 0, sizeof(cb)); + cb.cmd = attr; + cb.msg = *msg; + cb.info = info; + cb.fill = swconfig_send_port; + cb.close = swconfig_close_portlist; + + cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); + for (i = 0; i < val->len; i++) { + err = swconfig_send_multipart(&cb, &val->value.ports[i]); + if (err) + goto done; + } + err = val->len; + swconfig_close_portlist(&cb, NULL); + *msg = cb.msg; + +done: + return err; +} + +static int +swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr, + const struct switch_port_link *link) +{ + struct nlattr *p = NULL; + int err = 0; + + p = nla_nest_start(msg, attr); + if (link->link) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK)) + goto nla_put_failure; + } + if (link->duplex) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX)) + goto nla_put_failure; + } + if (link->aneg) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG)) + goto nla_put_failure; + } + if (link->tx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW)) + goto nla_put_failure; + } + if (link->rx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW)) + goto nla_put_failure; + } + if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed)) + goto nla_put_failure; + if (link->eee & ADVERTISED_100baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET)) + goto nla_put_failure; + } + if (link->eee & ADVERTISED_1000baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + + return err; + +nla_put_failure: + nla_nest_cancel(msg, p); + return -1; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attr *attr; + struct switch_dev *dev; + struct sk_buff *msg = NULL; + struct switch_val val; + int err = -EINVAL; + int cmd = hdr->cmd; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->get) + goto error; + + if (attr->type == SWITCH_TYPE_PORTS) { + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + } else if (attr->type == SWITCH_TYPE_LINK) { + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + } + + err = attr->get(dev, attr, &val); + if (err) + goto error; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + goto error; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + 0, cmd); + if (IS_ERR(hdr)) + goto nla_put_failure; + + switch (attr->type) { + case SWITCH_TYPE_INT: + if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) + goto nla_put_failure; + break; + case SWITCH_TYPE_STRING: + if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) + goto nla_put_failure; + break; + case SWITCH_TYPE_PORTS: + err = swconfig_send_ports(&msg, info, + SWITCH_ATTR_OP_VALUE_PORTS, &val); + if (err < 0) + goto nla_put_failure; + break; + case SWITCH_TYPE_LINK: + err = swconfig_send_link(msg, info, + SWITCH_ATTR_OP_VALUE_LINK, val.value.link); + if (err < 0) + goto nla_put_failure; + break; + default: + pr_debug("invalid type in attribute\n"); + err = -EINVAL; + goto nla_put_failure; + } + genlmsg_end(msg, hdr); + err = msg->len; + if (err < 0) + goto nla_put_failure; + + swconfig_put_dev(dev); + return genlmsg_reply(msg, info); + +nla_put_failure: + if (msg) + nlmsg_free(msg); +error: + swconfig_put_dev(dev); + if (!err) + err = -ENOMEM; + return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, + const struct switch_dev *dev) +{ + struct nlattr *p = NULL, *m = NULL; + void *hdr; + int i; + + hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, + SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) + goto nla_put_failure; + + m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); + if (!m) + goto nla_put_failure; + for (i = 0; i < dev->ports; i++) { + p = nla_nest_start(msg, SWITCH_ATTR_PORTS); + if (!p) + continue; + if (dev->portmap[i].s) { + if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, + dev->portmap[i].s)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, + dev->portmap[i].virt)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + } + nla_nest_end(msg, m); + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct switch_dev *dev; + int start = cb->args[0]; + int idx = 0; + + swconfig_lock(); + list_for_each_entry(dev, &swdevs, dev_list) { + if (++idx <= start) + continue; + if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + dev) < 0) + break; + } + swconfig_unlock(); + cb->args[0] = idx; + + return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ + return 0; +} + +static struct genl_ops swconfig_ops[] = { + { + .cmd = SWITCH_CMD_LIST_GLOBAL, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_VLAN, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_PORT, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_GLOBAL, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_VLAN, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_PORT, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_GLOBAL, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_VLAN, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_PORT, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_SWITCH, + .dumpit = swconfig_dump_switches, + .policy = switch_policy, + .done = swconfig_done, + } +}; + +static struct genl_family switch_fam = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + .id = GENL_ID_GENERATE, +#endif + .name = "switch", + .hdrsize = 0, + .version = 1, + .maxattr = SWITCH_ATTR_MAX, + .module = THIS_MODULE, + .ops = swconfig_ops, + .n_ops = ARRAY_SIZE(swconfig_ops), +}; + +#ifdef CONFIG_OF +void +of_switch_load_portmap(struct switch_dev *dev) +{ + struct device_node *port; + + if (!dev->of_node) + return; + + for_each_child_of_node(dev->of_node, port) { + const __be32 *prop; + const char *segment; + int size, phys; + + if (!of_device_is_compatible(port, "swconfig,port")) + continue; + + if (of_property_read_string(port, "swconfig,segment", &segment)) + continue; + + prop = of_get_property(port, "swconfig,portmap", &size); + if (!prop) + continue; + + if (size != (2 * sizeof(*prop))) { + pr_err("%s: failed to parse port mapping\n", + port->name); + continue; + } + + phys = be32_to_cpup(prop++); + if ((phys < 0) | (phys >= dev->ports)) { + pr_err("%s: physical port index out of range\n", + port->name); + continue; + } + + dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); + dev->portmap[phys].virt = be32_to_cpup(prop); + pr_debug("Found port: %s, physical: %d, virtual: %d\n", + segment, phys, dev->portmap[phys].virt); + } +} +#endif + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ + struct switch_dev *sdev; + const int max_switches = 8 * sizeof(unsigned long); + unsigned long in_use = 0; + int err; + int i; + + INIT_LIST_HEAD(&dev->dev_list); + if (netdev) { + dev->netdev = netdev; + if (!dev->alias) + dev->alias = netdev->name; + } + BUG_ON(!dev->alias); + + /* Make sure swdev_id doesn't overflow */ + if (swdev_id == INT_MAX) { + return -ENOMEM; + } + + if (dev->ports > 0) { + dev->portbuf = kzalloc(sizeof(struct switch_port) * + dev->ports, GFP_KERNEL); + if (!dev->portbuf) + return -ENOMEM; + dev->portmap = kzalloc(sizeof(struct switch_portmap) * + dev->ports, GFP_KERNEL); + if (!dev->portmap) { + kfree(dev->portbuf); + return -ENOMEM; + } + } + swconfig_defaults_init(dev); + mutex_init(&dev->sw_mutex); + swconfig_lock(); + dev->id = ++swdev_id; + + list_for_each_entry(sdev, &swdevs, dev_list) { + if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) + continue; + if (i < 0 || i > max_switches) + continue; + + set_bit(i, &in_use); + } + i = find_first_zero_bit(&in_use, max_switches); + + if (i == max_switches) { + swconfig_unlock(); + return -ENFILE; + } + +#ifdef CONFIG_OF + if (dev->ports) + of_switch_load_portmap(dev); +#endif + + /* fill device name */ + snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); + + list_add_tail(&dev->dev_list, &swdevs); + swconfig_unlock(); + + err = swconfig_create_led_trigger(dev); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ + swconfig_destroy_led_trigger(dev); + kfree(dev->portbuf); + mutex_lock(&dev->sw_mutex); + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + mutex_unlock(&dev->sw_mutex); +} +EXPORT_SYMBOL_GPL(unregister_switch); + +int +switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + if (WARN_ON(!dev->ops->phy_write16)) + return -ENOTSUPP; + + /* Generic implementation */ + if (link->aneg) { + dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); + dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + } else { + u16 bmcr = 0; + + if (link->duplex) + bmcr |= BMCR_FULLDPLX; + + switch (link->speed) { + case SWITCH_PORT_SPEED_10: + break; + case SWITCH_PORT_SPEED_100: + bmcr |= BMCR_SPEED100; + break; + case SWITCH_PORT_SPEED_1000: + bmcr |= BMCR_SPEED1000; + break; + default: + return -ENOTSUPP; + } + + dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); + } + + return 0; +} + +static int __init +swconfig_init(void) +{ + INIT_LIST_HEAD(&swdevs); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + return genl_register_family_with_ops(&switch_fam, swconfig_ops); +#else + return genl_register_family(&switch_fam); +#endif +} + +static void __exit +swconfig_exit(void) +{ + genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); diff --git a/target/linux/generic/files-3.18/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files-3.18/drivers/net/phy/swconfig_leds.c new file mode 100644 index 0000000000..91824b7cf6 --- /dev/null +++ b/target/linux/generic/files-3.18/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,556 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include +#include +#include +#include + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */ +#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \ + SWCONFIG_LED_PORT_SPEED_10 | \ + SWCONFIG_LED_PORT_SPEED_100 | \ + SWCONFIG_LED_PORT_SPEED_1000) + +#define SWCONFIG_LED_MODE_LINK 0x01 +#define SWCONFIG_LED_MODE_TX 0x02 +#define SWCONFIG_LED_MODE_RX 0x04 +#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) +#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \ + SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS]; + unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS]; + u8 link_speed[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; + u8 mode; + u8 speed_mask; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_set_brightness(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + int ret; + bool changed; + + ret = kstrtoul(buf, 0, &port_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + changed = (trig_data->port_mask != port_mask); + trig_data->port_mask = port_mask; + write_unlock(&trig_data->lock); + + if (changed) { + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + swconfig_trig_update_port_mask(led_cdev->trigger); + } + + return size; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u32 port_mask; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", port_mask); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +/* speed_mask file handler - display value */ +static ssize_t swconfig_trig_speed_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + + read_lock(&trig_data->lock); + speed_mask = trig_data->speed_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", speed_mask); + + return strlen(buf) + 1; +} + +/* speed_mask file handler - store value */ +static ssize_t swconfig_trig_speed_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + int ret; + + ret = kstrtou8(buf, 0, &speed_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL; + write_unlock(&trig_data->lock); + + return size; +} + +/* speed_mask special file */ +static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show, + swconfig_trig_speed_mask_store); + +static ssize_t swconfig_trig_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 mode; + + read_lock(&trig_data->lock); + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + if (mode == 0) { + strcpy(buf, "none\n"); + } else { + if (mode & SWCONFIG_LED_MODE_LINK) + strcat(buf, "link "); + if (mode & SWCONFIG_LED_MODE_TX) + strcat(buf, "tx "); + if (mode & SWCONFIG_LED_MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + return strlen(buf)+1; +} + +static ssize_t swconfig_trig_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode < 0) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= SWCONFIG_LED_MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= SWCONFIG_LED_MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= SWCONFIG_LED_MODE_LINK; + else + return -EINVAL; + } + + if (new_mode < 0) + return -EINVAL; + + write_lock(&trig_data->lock); + trig_data->mode = (u8)new_mode; + write_unlock(&trig_data->lock); + + return size; +} + +/* mode special file */ +static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show, + swconfig_trig_mode_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL; + trig_data->mode = SWCONFIG_LED_MODE_ALL; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + err = device_create_file(led_cdev->dev, &dev_attr_speed_mask); + if (err) + goto err_dev_free; + + err = device_create_file(led_cdev->dev, &dev_attr_mode); + if (err) + goto err_mode_free; + + return; + +err_mode_free: + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + +err_dev_free: + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + device_remove_file(led_cdev->dev, &dev_attr_mode); + kfree(trig_data); + } +} + +/* + * link off -> led off (can't be any other reason to turn it on) + * link on: + * mode link: led on by default only if speed matches, else off + * mode txrx: blink only if speed matches, else off + */ +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + u8 speed_mask, mode; + enum led_brightness led_base, led_blink; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + speed_mask = trig_data->speed_mask; + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */ + } + else { + unsigned long traffic; + int speedok; /* link speed flag */ + int i; + + led_base = LED_FULL; + led_blink = LED_OFF; + traffic = 0; + speedok = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) { + if (sw_trig->link_speed[i] & speed_mask) { + traffic += ((mode & SWCONFIG_LED_MODE_TX) ? + sw_trig->port_tx_traffic[i] : 0) + + ((mode & SWCONFIG_LED_MODE_RX) ? + sw_trig->port_rx_traffic[i] : 0); + speedok = 1; + } + } + } + + if (speedok) { + /* At least one port speed matches speed_mask */ + if (!(mode & SWCONFIG_LED_MODE_LINK)) { + led_base = LED_OFF; + led_blink = LED_FULL; + } + + if (trig_data->prev_brightness != led_base) + swconfig_trig_set_brightness(trig_data, + led_base); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, + led_blink); + } else if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + sw_trig->link_speed[i] = 0; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) { + link |= port_bit; + switch (port_link.speed) { + case SWITCH_PORT_SPEED_UNKNOWN: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_NA; + break; + case SWITCH_PORT_SPEED_10: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_1000; + break; + } + } + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_tx_traffic[i] = port_stats.tx_bytes; + sw_trig->port_rx_traffic[i] = port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files-3.18/include/linux/ar8216_platform.h b/target/linux/generic/files-3.18/include/linux/ar8216_platform.h new file mode 100644 index 0000000000..24bc442a26 --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/ar8216_platform.h @@ -0,0 +1,133 @@ +/* + * AR8216 switch driver platform data + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef AR8216_PLATFORM_H +#define AR8216_PLATFORM_H + +enum ar8327_pad_mode { + AR8327_PAD_NC = 0, + AR8327_PAD_MAC2MAC_MII, + AR8327_PAD_MAC2MAC_GMII, + AR8327_PAD_MAC_SGMII, + AR8327_PAD_MAC2PHY_MII, + AR8327_PAD_MAC2PHY_GMII, + AR8327_PAD_MAC_RGMII, + AR8327_PAD_PHY_GMII, + AR8327_PAD_PHY_RGMII, + AR8327_PAD_PHY_MII, +}; + +enum ar8327_clk_delay_sel { + AR8327_CLK_DELAY_SEL0 = 0, + AR8327_CLK_DELAY_SEL1, + AR8327_CLK_DELAY_SEL2, + AR8327_CLK_DELAY_SEL3, +}; + +struct ar8327_pad_cfg { + enum ar8327_pad_mode mode; + bool rxclk_sel; + bool txclk_sel; + bool pipe_rxclk_sel; + bool txclk_delay_en; + bool rxclk_delay_en; + bool sgmii_delay_en; + enum ar8327_clk_delay_sel txclk_delay_sel; + enum ar8327_clk_delay_sel rxclk_delay_sel; + bool mac06_exchange_dis; +}; + +enum ar8327_port_speed { + AR8327_PORT_SPEED_10 = 0, + AR8327_PORT_SPEED_100, + AR8327_PORT_SPEED_1000, +}; + +struct ar8327_port_cfg { + int force_link:1; + enum ar8327_port_speed speed; + int txpause:1; + int rxpause:1; + int duplex:1; +}; + +struct ar8327_sgmii_cfg { + u32 sgmii_ctrl; + bool serdes_aen; +}; + +struct ar8327_led_cfg { + u32 led_ctrl0; + u32 led_ctrl1; + u32 led_ctrl2; + u32 led_ctrl3; + bool open_drain; +}; + +enum ar8327_led_num { + AR8327_LED_PHY0_0 = 0, + AR8327_LED_PHY0_1, + AR8327_LED_PHY0_2, + AR8327_LED_PHY1_0, + AR8327_LED_PHY1_1, + AR8327_LED_PHY1_2, + AR8327_LED_PHY2_0, + AR8327_LED_PHY2_1, + AR8327_LED_PHY2_2, + AR8327_LED_PHY3_0, + AR8327_LED_PHY3_1, + AR8327_LED_PHY3_2, + AR8327_LED_PHY4_0, + AR8327_LED_PHY4_1, + AR8327_LED_PHY4_2, +}; + +enum ar8327_led_mode { + AR8327_LED_MODE_HW = 0, + AR8327_LED_MODE_SW, +}; + +struct ar8327_led_info { + const char *name; + const char *default_trigger; + bool active_low; + enum ar8327_led_num led_num; + enum ar8327_led_mode mode; +}; + +#define AR8327_LED_INFO(_led, _mode, _name) { \ + .name = (_name), \ + .led_num = AR8327_LED_ ## _led, \ + .mode = AR8327_LED_MODE_ ## _mode \ +} + +struct ar8327_platform_data { + struct ar8327_pad_cfg *pad0_cfg; + struct ar8327_pad_cfg *pad5_cfg; + struct ar8327_pad_cfg *pad6_cfg; + struct ar8327_sgmii_cfg *sgmii_cfg; + struct ar8327_port_cfg port0_cfg; + struct ar8327_port_cfg port6_cfg; + struct ar8327_led_cfg *led_cfg; + + int (*get_port_link)(unsigned port); + + unsigned num_leds; + const struct ar8327_led_info *leds; +}; + +#endif /* AR8216_PLATFORM_H */ + diff --git a/target/linux/generic/files-3.18/include/linux/ath5k_platform.h b/target/linux/generic/files-3.18/include/linux/ath5k_platform.h new file mode 100644 index 0000000000..ec85224528 --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/ath5k_platform.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * Copyright (c) 2010 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH5K_PLATFORM_H +#define _LINUX_ATH5K_PLATFORM_H + +#define ATH5K_PLAT_EEP_MAX_WORDS 2048 + +struct ath5k_platform_data { + u16 *eeprom_data; + u8 *macaddr; +}; + +#endif /* _LINUX_ATH5K_PLATFORM_H */ diff --git a/target/linux/generic/files-3.18/include/linux/ath9k_platform.h b/target/linux/generic/files-3.18/include/linux/ath9k_platform.h new file mode 100644 index 0000000000..f1f2ad419c --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/ath9k_platform.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH9K_PLATFORM_H +#define _LINUX_ATH9K_PLATFORM_H + +#define ATH9K_PLAT_EEP_MAX_WORDS 2048 + +struct ath9k_platform_data { + const char *eeprom_name; + + u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS]; + u8 *macaddr; + + int led_pin; + u32 gpio_mask; + u32 gpio_val; + + u32 bt_active_pin; + u32 bt_priority_pin; + u32 wlan_active_pin; + + bool endian_check; + bool is_clk_25mhz; + bool tx_gain_buffalo; + bool disable_2ghz; + bool disable_5ghz; + bool led_active_high; + + int (*get_mac_revision)(void); + int (*external_reset)(void); + + bool use_eeprom; + + int num_leds; + const struct gpio_led *leds; + + unsigned num_btns; + const struct gpio_keys_button *btns; + unsigned btn_poll_interval; + + bool ubnt_hsr; +}; + +#endif /* _LINUX_ATH9K_PLATFORM_H */ diff --git a/target/linux/generic/files-3.18/include/linux/myloader.h b/target/linux/generic/files-3.18/include/linux/myloader.h new file mode 100644 index 0000000000..d89e415fba --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/myloader.h @@ -0,0 +1,121 @@ +/* + * Compex's MyLoader specific definitions + * + * Copyright (C) 2006-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MYLOADER_H_ +#define _MYLOADER_H_ + +/* Myloader specific magic numbers */ +#define MYLO_MAGIC_SYS_PARAMS 0x20021107 +#define MYLO_MAGIC_PARTITIONS 0x20021103 +#define MYLO_MAGIC_BOARD_PARAMS 0x20021103 + +/* Vendor ID's (seems to be same as the PCI vendor ID's) */ +#define VENID_COMPEX 0x11F6 + +/* Devices based on the ADM5120 */ +#define DEVID_COMPEX_NP27G 0x0078 +#define DEVID_COMPEX_NP28G 0x044C +#define DEVID_COMPEX_NP28GHS 0x044E +#define DEVID_COMPEX_WP54Gv1C 0x0514 +#define DEVID_COMPEX_WP54G 0x0515 +#define DEVID_COMPEX_WP54AG 0x0546 +#define DEVID_COMPEX_WPP54AG 0x0550 +#define DEVID_COMPEX_WPP54G 0x0555 + +/* Devices based on the Atheros AR2317 */ +#define DEVID_COMPEX_NP25G 0x05E6 +#define DEVID_COMPEX_WPE53G 0x05DC + +/* Devices based on the Atheros AR71xx */ +#define DEVID_COMPEX_WP543 0x0640 +#define DEVID_COMPEX_WPE72 0x0672 + +/* Devices based on the IXP422 */ +#define DEVID_COMPEX_WP18 0x047E +#define DEVID_COMPEX_NP18A 0x0489 + +/* Other devices */ +#define DEVID_COMPEX_NP26G8M 0x03E8 +#define DEVID_COMPEX_NP26G16M 0x03E9 + +struct mylo_partition { + uint16_t flags; /* partition flags */ + uint16_t type; /* type of the partition */ + uint32_t addr; /* relative address of the partition from the + flash start */ + uint32_t size; /* size of the partition in bytes */ + uint32_t param; /* if this is the active partition, the + MyLoader load code to this address */ +}; + +#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition, + * MyLoader loads firmware from here */ +#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */ +#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */ +#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM + * before decompression */ +#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */ +#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */ + +#define PARTITION_TYPE_FREE 0 +#define PARTITION_TYPE_USED 1 + +#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the + partition table */ + +struct mylo_partition_table { + uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */ + uint32_t res0; /* unknown/unused */ + uint32_t res1; /* unknown/unused */ + uint32_t res2; /* unknown/unused */ + struct mylo_partition partitions[MYLO_MAX_PARTITIONS]; +}; + +struct mylo_partition_header { + uint32_t len; /* length of the partition data */ + uint32_t crc; /* CRC value of the partition data */ +}; + +struct mylo_system_params { + uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t mylo_ver; + uint16_t vid; /* Vendor ID */ + uint16_t did; /* Device ID */ + uint16_t svid; /* Sub Vendor ID */ + uint16_t sdid; /* Sub Device ID */ + uint32_t rev; /* device revision */ + uint32_t fwhi; + uint32_t fwlo; + uint32_t tftp_addr; + uint32_t prog_start; + uint32_t flash_size; /* size of boot FLASH in bytes */ + uint32_t dram_size; /* size of onboard RAM in bytes */ +}; + +struct mylo_eth_addr { + uint8_t mac[6]; + uint8_t csum[2]; +}; + +#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address + in the board parameters */ + +struct mylo_board_params { + uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t res2; + struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT]; +}; + +#endif /* _MYLOADER_H_*/ diff --git a/target/linux/generic/files-3.18/include/linux/platform_data/adm6996-gpio.h b/target/linux/generic/files-3.18/include/linux/platform_data/adm6996-gpio.h new file mode 100644 index 0000000000..d5af9bbf6e --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/platform_data/adm6996-gpio.h @@ -0,0 +1,29 @@ +/* + * ADM6996 GPIO platform data + * + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __PLATFORM_ADM6996_GPIO_H +#define __PLATFORM_ADM6996_GPIO_H + +#include + +enum adm6996_model { + ADM6996FC = 1, + ADM6996M = 2, + ADM6996L = 3, +}; + +struct adm6996_gpio_platform_data { + u8 eecs; + u8 eesk; + u8 eedi; + enum adm6996_model model; +}; + +#endif diff --git a/target/linux/generic/files-3.18/include/linux/platform_data/b53.h b/target/linux/generic/files-3.18/include/linux/platform_data/b53.h new file mode 100644 index 0000000000..78427417a1 --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/platform_data/b53.h @@ -0,0 +1,36 @@ +/* + * B53 platform data + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_H +#define __B53_H + +#include + +struct b53_platform_data { + u32 chip_id; + u16 enabled_ports; + + /* allow to specify an ethX alias */ + const char *alias; + + /* only used by MMAP'd driver */ + unsigned big_endian:1; + void __iomem *regs; +}; + +#endif diff --git a/target/linux/generic/files-3.18/include/linux/routerboot.h b/target/linux/generic/files-3.18/include/linux/routerboot.h new file mode 100644 index 0000000000..3cda858cf9 --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/routerboot.h @@ -0,0 +1,106 @@ +/* + * Mikrotik's RouterBOOT definitions + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _ROUTERBOOT_H +#define _ROUTERBOOT_H + +#define RB_MAC_SIZE 6 + +/* + * Magic numbers + */ +#define RB_MAGIC_HARD 0x64726148 /* "Hard" */ +#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */ +#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */ + +#define RB_ID_TERMINATOR 0 + +/* + * ID values for Hardware settings + */ +#define RB_ID_HARD_01 1 +#define RB_ID_HARD_02 2 +#define RB_ID_FLASH_INFO 3 +#define RB_ID_MAC_ADDRESS_PACK 4 +#define RB_ID_BOARD_NAME 5 +#define RB_ID_BIOS_VERSION 6 +#define RB_ID_HARD_07 7 +#define RB_ID_SDRAM_TIMINGS 8 +#define RB_ID_DEVICE_TIMINGS 9 +#define RB_ID_SOFTWARE_ID 10 +#define RB_ID_SERIAL_NUMBER 11 +#define RB_ID_HARD_12 12 +#define RB_ID_MEMORY_SIZE 13 +#define RB_ID_MAC_ADDRESS_COUNT 14 +#define RB_ID_HW_OPTIONS 21 +#define RB_ID_WLAN_DATA 22 + +/* + * ID values for Software settings + */ +#define RB_ID_UART_SPEED 1 +#define RB_ID_BOOT_DELAY 2 +#define RB_ID_BOOT_DEVICE 3 +#define RB_ID_BOOT_KEY 4 +#define RB_ID_CPU_MODE 5 +#define RB_ID_FW_VERSION 6 +#define RB_ID_SOFT_07 7 +#define RB_ID_SOFT_08 8 +#define RB_ID_BOOT_PROTOCOL 9 +#define RB_ID_SOFT_10 10 +#define RB_ID_SOFT_11 11 + +/* + * UART_SPEED values + */ +#define RB_UART_SPEED_115200 0 +#define RB_UART_SPEED_57600 1 +#define RB_UART_SPEED_38400 2 +#define RB_UART_SPEED_19200 3 +#define RB_UART_SPEED_9600 4 +#define RB_UART_SPEED_4800 5 +#define RB_UART_SPEED_2400 6 +#define RB_UART_SPEED_1200 7 + +/* + * BOOT_DELAY values + */ +#define RB_BOOT_DELAY_0SEC 0 +#define RB_BOOT_DELAY_1SEC 1 +#define RB_BOOT_DELAY_2SEC 2 + +/* + * BOOT_DEVICE values + */ +#define RB_BOOT_DEVICE_ETHER 0 +#define RB_BOOT_DEVICE_NANDETH 1 +#define RB_BOOT_DEVICE_ETHONCE 2 +#define RB_BOOT_DEVICE_NANDONLY 3 + +/* + * BOOT_KEY values + */ +#define RB_BOOT_KEY_ANY 0 +#define RB_BOOT_KEY_DEL 1 + +/* + * CPU_MODE values + */ +#define RB_CPU_MODE_POWERSAVE 0 +#define RB_CPU_MODE_REGULAR 1 + +/* + * BOOT_PROTOCOL values + */ +#define RB_BOOT_PROTOCOL_BOOTP 0 +#define RB_BOOT_PROTOCOL_DHCP 1 + +#endif /* _ROUTERBOOT_H */ diff --git a/target/linux/generic/files-3.18/include/linux/rt2x00_platform.h b/target/linux/generic/files-3.18/include/linux/rt2x00_platform.h new file mode 100644 index 0000000000..e10377e21b --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/rt2x00_platform.h @@ -0,0 +1,23 @@ +/* + * Platform data definition for the rt2x00 driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _RT2X00_PLATFORM_H +#define _RT2X00_PLATFORM_H + +struct rt2x00_platform_data { + char *eeprom_file_name; + const u8 *mac_address; + + int disable_2ghz; + int disable_5ghz; +}; + +#endif /* _RT2X00_PLATFORM_H */ diff --git a/target/linux/generic/files-3.18/include/linux/rtl8366.h b/target/linux/generic/files-3.18/include/linux/rtl8366.h new file mode 100644 index 0000000000..e3ce8f5361 --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/rtl8366.h @@ -0,0 +1,42 @@ +/* + * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_H +#define _RTL8366_H + +#define RTL8366_DRIVER_NAME "rtl8366" +#define RTL8366S_DRIVER_NAME "rtl8366s" +#define RTL8366RB_DRIVER_NAME "rtl8366rb" + +struct rtl8366_smi; + +enum rtl8366_type { + RTL8366_TYPE_UNKNOWN, + RTL8366_TYPE_S, + RTL8366_TYPE_RB, +}; + +struct rtl8366_initval { + unsigned reg; + u16 val; +}; + +struct rtl8366_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + + unsigned num_initvals; + struct rtl8366_initval *initvals; +}; + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata); + +#endif /* _RTL8366_H */ diff --git a/target/linux/generic/files-3.18/include/linux/rtl8367.h b/target/linux/generic/files-3.18/include/linux/rtl8367.h new file mode 100644 index 0000000000..855de6a5cc --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/rtl8367.h @@ -0,0 +1,60 @@ +/* + * Platform data definition for the Realtek RTL8367 ethernet switch driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8367_H +#define _RTL8367_H + +#define RTL8367_DRIVER_NAME "rtl8367" +#define RTL8367B_DRIVER_NAME "rtl8367b" + +enum rtl8367_port_speed { + RTL8367_PORT_SPEED_10 = 0, + RTL8367_PORT_SPEED_100, + RTL8367_PORT_SPEED_1000, +}; + +struct rtl8367_port_ability { + int force_mode; + int nway; + int txpause; + int rxpause; + int link; + int duplex; + enum rtl8367_port_speed speed; +}; + +enum rtl8367_extif_mode { + RTL8367_EXTIF_MODE_DISABLED = 0, + RTL8367_EXTIF_MODE_RGMII, + RTL8367_EXTIF_MODE_MII_MAC, + RTL8367_EXTIF_MODE_MII_PHY, + RTL8367_EXTIF_MODE_TMII_MAC, + RTL8367_EXTIF_MODE_TMII_PHY, + RTL8367_EXTIF_MODE_GMII, + RTL8367_EXTIF_MODE_RGMII_33V, +}; + +struct rtl8367_extif_config { + unsigned int txdelay; + unsigned int rxdelay; + enum rtl8367_extif_mode mode; + struct rtl8367_port_ability ability; +}; + +struct rtl8367_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(bool active); + + struct rtl8367_extif_config *extif0_cfg; + struct rtl8367_extif_config *extif1_cfg; +}; + +#endif /* _RTL8367_H */ diff --git a/target/linux/generic/files-3.18/include/linux/switch.h b/target/linux/generic/files-3.18/include/linux/switch.h new file mode 100644 index 0000000000..4e6238470d --- /dev/null +++ b/target/linux/generic/files-3.18/include/linux/switch.h @@ -0,0 +1,179 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _LINUX_SWITCH_H +#define _LINUX_SWITCH_H + +#include +#include + +struct switch_dev; +struct switch_op; +struct switch_val; +struct switch_attr; +struct switch_attrlist; +struct switch_led_trigger; + +int register_switch(struct switch_dev *dev, struct net_device *netdev); +void unregister_switch(struct switch_dev *dev); + +/** + * struct switch_attrlist - attribute list + * + * @n_attr: number of attributes + * @attr: pointer to the attributes array + */ +struct switch_attrlist { + int n_attr; + const struct switch_attr *attr; +}; + +enum switch_port_speed { + SWITCH_PORT_SPEED_UNKNOWN = 0, + SWITCH_PORT_SPEED_10 = 10, + SWITCH_PORT_SPEED_100 = 100, + SWITCH_PORT_SPEED_1000 = 1000, +}; + +struct switch_port_link { + bool link; + bool duplex; + bool aneg; + bool tx_flow; + bool rx_flow; + enum switch_port_speed speed; + /* in ethtool adv_t format */ + u32 eee; +}; + +struct switch_port_stats { + unsigned long long tx_bytes; + unsigned long long rx_bytes; +}; + +/** + * struct switch_dev_ops - switch driver operations + * + * @attr_global: global switch attribute list + * @attr_port: port attribute list + * @attr_vlan: vlan attribute list + * + * Callbacks: + * + * @get_vlan_ports: read the port list of a VLAN + * @set_vlan_ports: set the port list of a VLAN + * + * @get_port_pvid: get the primary VLAN ID of a port + * @set_port_pvid: set the primary VLAN ID of a port + * + * @apply_config: apply all changed settings to the switch + * @reset_switch: resetting the switch + */ +struct switch_dev_ops { + struct switch_attrlist attr_global, attr_port, attr_vlan; + + int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + + int (*get_port_pvid)(struct switch_dev *dev, int port, int *val); + int (*set_port_pvid)(struct switch_dev *dev, int port, int val); + + int (*apply_config)(struct switch_dev *dev); + int (*reset_switch)(struct switch_dev *dev); + + int (*get_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*set_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*get_port_stats)(struct switch_dev *dev, int port, + struct switch_port_stats *stats); + + int (*phy_read16)(struct switch_dev *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct switch_dev *dev, int addr, u8 reg, u16 value); +}; + +struct switch_dev { + struct device_node *of_node; + const struct switch_dev_ops *ops; + /* will be automatically filled */ + char devname[IFNAMSIZ]; + + const char *name; + /* NB: either alias or netdev must be set */ + const char *alias; + struct net_device *netdev; + + unsigned int ports; + unsigned int vlans; + unsigned int cpu_port; + + /* the following fields are internal for swconfig */ + unsigned int id; + struct list_head dev_list; + unsigned long def_global, def_port, def_vlan; + + struct mutex sw_mutex; + struct switch_port *portbuf; + struct switch_portmap *portmap; + struct switch_port_link linkbuf; + + char buf[128]; + +#ifdef CONFIG_SWCONFIG_LEDS + struct switch_led_trigger *led_trigger; +#endif +}; + +struct switch_port { + u32 id; + u32 flags; +}; + +struct switch_portmap { + u32 virt; + const char *s; +}; + +struct switch_val { + const struct switch_attr *attr; + unsigned int port_vlan; + unsigned int len; + union { + const char *s; + u32 i; + struct switch_port *ports; + struct switch_port_link *link; + } value; +}; + +struct switch_attr { + int disabled; + int type; + const char *name; + const char *description; + + int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + + /* for driver internal use */ + int id; + int ofs; + int max; +}; + +int switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link); + +#endif /* _LINUX_SWITCH_H */ diff --git a/target/linux/generic/files-3.18/include/uapi/linux/switch.h b/target/linux/generic/files-3.18/include/uapi/linux/switch.h new file mode 100644 index 0000000000..ea449653fa --- /dev/null +++ b/target/linux/generic/files-3.18/include/uapi/linux/switch.h @@ -0,0 +1,119 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _UAPI_LINUX_SWITCH_H +#define _UAPI_LINUX_SWITCH_H + +#include +#include +#include +#include +#ifndef __KERNEL__ +#include +#include +#include +#endif + +/* main attributes */ +enum { + SWITCH_ATTR_UNSPEC, + /* global */ + SWITCH_ATTR_TYPE, + /* device */ + SWITCH_ATTR_ID, + SWITCH_ATTR_DEV_NAME, + SWITCH_ATTR_ALIAS, + SWITCH_ATTR_NAME, + SWITCH_ATTR_VLANS, + SWITCH_ATTR_PORTS, + SWITCH_ATTR_PORTMAP, + SWITCH_ATTR_CPU_PORT, + /* attributes */ + SWITCH_ATTR_OP_ID, + SWITCH_ATTR_OP_TYPE, + SWITCH_ATTR_OP_NAME, + SWITCH_ATTR_OP_PORT, + SWITCH_ATTR_OP_VLAN, + SWITCH_ATTR_OP_VALUE_INT, + SWITCH_ATTR_OP_VALUE_STR, + SWITCH_ATTR_OP_VALUE_PORTS, + SWITCH_ATTR_OP_VALUE_LINK, + SWITCH_ATTR_OP_DESCRIPTION, + /* port lists */ + SWITCH_ATTR_PORT, + SWITCH_ATTR_MAX +}; + +enum { + /* port map */ + SWITCH_PORTMAP_PORTS, + SWITCH_PORTMAP_SEGMENT, + SWITCH_PORTMAP_VIRT, + SWITCH_PORTMAP_MAX +}; + +/* commands */ +enum { + SWITCH_CMD_UNSPEC, + SWITCH_CMD_GET_SWITCH, + SWITCH_CMD_NEW_ATTR, + SWITCH_CMD_LIST_GLOBAL, + SWITCH_CMD_GET_GLOBAL, + SWITCH_CMD_SET_GLOBAL, + SWITCH_CMD_LIST_PORT, + SWITCH_CMD_GET_PORT, + SWITCH_CMD_SET_PORT, + SWITCH_CMD_LIST_VLAN, + SWITCH_CMD_GET_VLAN, + SWITCH_CMD_SET_VLAN +}; + +/* data types */ +enum switch_val_type { + SWITCH_TYPE_UNSPEC, + SWITCH_TYPE_INT, + SWITCH_TYPE_STRING, + SWITCH_TYPE_PORTS, + SWITCH_TYPE_LINK, + SWITCH_TYPE_NOVAL, +}; + +/* port nested attributes */ +enum { + SWITCH_PORT_UNSPEC, + SWITCH_PORT_ID, + SWITCH_PORT_FLAG_TAGGED, + SWITCH_PORT_ATTR_MAX +}; + +/* link nested attributes */ +enum { + SWITCH_LINK_UNSPEC, + SWITCH_LINK_FLAG_LINK, + SWITCH_LINK_FLAG_DUPLEX, + SWITCH_LINK_FLAG_ANEG, + SWITCH_LINK_FLAG_TX_FLOW, + SWITCH_LINK_FLAG_RX_FLOW, + SWITCH_LINK_SPEED, + SWITCH_LINK_FLAG_EEE_100BASET, + SWITCH_LINK_FLAG_EEE_1000BASET, + SWITCH_LINK_ATTR_MAX, +}; + +#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000 + + +#endif /* _UAPI_LINUX_SWITCH_H */ diff --git a/target/linux/generic/files-4.14/Documentation/networking/adm6996.txt b/target/linux/generic/files-4.14/Documentation/networking/adm6996.txt new file mode 100644 index 0000000000..ab59f1df03 --- /dev/null +++ b/target/linux/generic/files-4.14/Documentation/networking/adm6996.txt @@ -0,0 +1,110 @@ +------- + +ADM6996FC / ADM6996M switch chip driver + + +1. General information + + This driver supports the FC and M models only. The ADM6996F and L are + completely different chips. + + Support for the FC model is extremely limited at the moment. There is no VLAN + support as of yet. The driver will not offer an swconfig interface for the FC + chip. + +1.1 VLAN IDs + + It is possible to define 16 different VLANs. Every VLAN has an identifier, its + VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the + swconfig based configuration is very straightforward. To define two VLANs with + IDs 4 and 5, you can invoke, for example: + + # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' + # swconfig dev ethX vlan 5 set ports '0t 1t 5t' + + The swconfig framework will automatically invoke 'port Y set pvid Z' for every + port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In + this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port + is the VLAN ID associated with untagged packets coming in on that port. + + But if you wish to use VLAN IDs outside the range 0-15, this automatic + behaviour of the swconfig framework becomes a problem. The 16 VLANs that + swconfig can configure on the ADM6996 also have a "vid" setting. By default, + this is the same as the number of the VLAN entry, to make the simple behaviour + above possible. To still support a VLAN with a VLAN ID higher than 15 + (presumably because you are in a network where such VLAN IDs are already in + use), you can change the "vid" setting of the VLAN to anything in the range + 0-1023. But suppose you did the following: + + # swconfig dev ethX vlan 0 set vid 998 + # swconfig dev ethX vlan 0 set ports '0 2 5t' + + Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid + 0'. But the "pvid" should be set to 998, so you are responsible for manually + fixing this! + +1.2 VLAN filtering + + The switch is configured to apply source port filtering. This means that + packets are only accepted when the port the packets came in on is a member of + the VLAN the packet should go to. + + Only membership of a VLAN is tested, it does not matter whether it is a tagged + or untagged membership. + + For untagged packets, the destination VLAN is the Primary VLAN ID of the + incoming port. So if the PVID of a port is 0, but that port is not a member of + the VLAN with ID 0, this means that untagged packets on that port are dropped. + This can be used as a roundabout way of dropping untagged packets from a port, + a mode often referred to as "Admit only tagged packets". + +1.3 Reset + + The two supported chip models do not have a sofware-initiated reset. When the + driver is initialised, as well as when the 'reset' swconfig option is invoked, + the driver will set those registers it knows about and supports to the correct + default value. But there are a lot of registers in the chip that the driver + does not support. If something changed those registers, invoking 'reset' or + performing a warm reboot might still leave the chip in a "broken" state. Only + a hardware reset will bring it back in the default state. + +2. Technical details on PHYs and the ADM6996 + + From the viewpoint of the Linux kernel, it is common that an Ethernet adapter + can be seen as a separate MAC entity and a separate PHY entity. The PHY entity + can be queried and set through registers accessible via an MDIO bus. A PHY + normally has a single address on that bus, in the range 0 through 31. + + The ADM6996 has special-purpose registers in the range of PHYs 0 through 10. + Even though all these registers control a single ADM6996 chip, the Linux + kernel treats this as 11 separate PHYs. The driver will bind to these + addresses to prevent a different PHY driver from binding and corrupting these + registers. + + What Linux sees as the PHY on address 0 is meant for the Ethernet MAC + connected to the CPU port of the ADM6996 switch chip (port 5). This is the + Ethernet MAC you will use to send and receive data through the switch. + + The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of + the switch chip. These can be accessed with the Generic PHY driver, as the + registers have the common layout. + + If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC + needs to bind to PHY address 20 for the port to work correctly. + + The ADM6996 switch driver will reset the ports 0 through 3 on startup and when + 'reset' is invoked. This could clash with a different PHY driver if the kernel + binds a PHY driver to address 16 through 19. + + If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996 + driver will simply always report a connected 100 Mbit/s full-duplex link for + that PHY, and provide no other functionality. This is most likely not what you + want. So if you see a message in your log + + ethX: PHY overlaps ADM6996, providing fixed PHY yy. + + This is most likely an indication that ethX will not work properly, and your + kernel needs to be configured to attach a different PHY to that Ethernet MAC. + + Controlling the mapping between MACs and PHYs is usually done in platform- or + board-specific fixup code. The ADM6996 driver has no influence over this. diff --git a/target/linux/generic/files-4.14/arch/mips/fw/myloader/Makefile b/target/linux/generic/files-4.14/arch/mips/fw/myloader/Makefile new file mode 100644 index 0000000000..34acfd01cc --- /dev/null +++ b/target/linux/generic/files-4.14/arch/mips/fw/myloader/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Compex's MyLoader support on MIPS architecture +# + +lib-y += myloader.o diff --git a/target/linux/generic/files-4.14/arch/mips/fw/myloader/myloader.c b/target/linux/generic/files-4.14/arch/mips/fw/myloader/myloader.c new file mode 100644 index 0000000000..a26f9ad3fd --- /dev/null +++ b/target/linux/generic/files-4.14/arch/mips/fw/myloader/myloader.c @@ -0,0 +1,63 @@ +/* + * Compex's MyLoader specific prom routines + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include + +#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800) +#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00) +#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00) +#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00) + +static struct myloader_info myloader_info __initdata; +static int myloader_found __initdata; + +struct myloader_info * __init myloader_get_info(void) +{ + struct mylo_system_params *sysp; + struct mylo_board_params *boardp; + struct mylo_partition_table *parts; + + if (myloader_found) + return &myloader_info; + + sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR); + boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR); + parts = (struct mylo_partition_table *)(PART_TABLE_ADDR); + + printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n", + sysp->magic, boardp->magic, parts->magic); + + /* Check for some magic numbers */ + if (sysp->magic != MYLO_MAGIC_SYS_PARAMS || + boardp->magic != MYLO_MAGIC_BOARD_PARAMS || + le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS) + return NULL; + + printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n", + sysp->vid, sysp->did, sysp->svid, sysp->sdid); + + myloader_info.vid = sysp->vid; + myloader_info.did = sysp->did; + myloader_info.svid = sysp->svid; + myloader_info.sdid = sysp->sdid; + + memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs)); + + myloader_found = 1; + + return &myloader_info; +} diff --git a/target/linux/generic/files-4.14/drivers/leds/ledtrig-netdev.c b/target/linux/generic/files-4.14/drivers/leds/ledtrig-netdev.c new file mode 100644 index 0000000000..8d3249010d --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/leds/ledtrig-netdev.c @@ -0,0 +1,444 @@ +/* + * LED Kernel Netdev Trigger + * + * Toggles the LED to reflect the link and traffic state of a named net device + * + * Copyright 2007 Oliver Jowett + * + * Derived from ledtrig-timer.c which is: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds.h" + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * + * interval - duration of LED blink, in milliseconds + * + * mode - either "none" (LED is off) or a space separated list of one or more of: + * link: LED's normal state reflects whether the link is up (has carrier) or not + * tx: LED blinks on transmitted data + * rx: LED blinks on receive data + * + * Some suggestions: + * + * Simple link status LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo link >someled/mode + * + * Ethernet-style link/activity LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo "link tx rx" >someled/mode + * + * Modem-style tx/rx LEDs: + * $ echo netdev >led1/trigger + * $ echo ppp0 >led1/device_name + * $ echo tx >led1/mode + * $ echo netdev >led2/trigger + * $ echo ppp0 >led2/device_name + * $ echo rx >led2/mode + * + */ + +#define MODE_LINK 1 +#define MODE_TX 2 +#define MODE_RX 4 + +struct led_netdev_data { + spinlock_t lock; + + struct delayed_work work; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + unsigned interval; + unsigned mode; + unsigned link_up; + unsigned last_activity; +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) + led_set_brightness(trigger_data->led_cdev, LED_FULL); + else + led_set_brightness(trigger_data->led_cdev, LED_OFF); + + if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static ssize_t led_device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%s\n", trigger_data->device_name); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (size >= IFNAMSIZ) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + strcpy(trigger_data->device_name, buf); + if (size > 0 && trigger_data->device_name[size-1] == '\n') + trigger_data->device_name[size-1] = 0; + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + if (trigger_data->device_name[0] != 0) { + /* check for existing device to update from */ + trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); + if (trigger_data->net_dev != NULL) + trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; + } + + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); + +static ssize_t led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->mode == 0) { + strcpy(buf, "none\n"); + } else { + if (trigger_data->mode & MODE_LINK) + strcat(buf, "link "); + if (trigger_data->mode & MODE_TX) + strcat(buf, "tx "); + if (trigger_data->mode & MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf)+1; +} + +static ssize_t led_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode == -1) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= MODE_LINK; + else + return -EINVAL; + } + + if (new_mode == -1) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->mode = new_mode; + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); + +static ssize_t led_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long value = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (isspace(*after)) + count++; + + /* impose some basic bounds on the timer interval */ + if (count == size && value >= 5 && value <= 10000) { + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->interval = msecs_to_jiffies(value); + set_baseline_state(trigger_data); /* resets timer */ + spin_unlock_bh(&trigger_data->lock); + + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, + void *dv) +{ + struct net_device *dev = netdev_notifier_info_to_dev((struct netdev_notifier_info *) dv); + struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER && evt != NETDEV_CHANGENAME) + return NOTIFY_DONE; + + if (strcmp(dev->name, trigger_data->device_name)) + return NOTIFY_DONE; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (evt == NETDEV_REGISTER || evt == NETDEV_CHANGENAME) { + if (trigger_data->net_dev != NULL) + dev_put(trigger_data->net_dev); + + dev_hold(dev); + trigger_data->net_dev = dev; + trigger_data->link_up = 0; + goto done; + } + + if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + goto done; + } + + /* UP / DOWN / CHANGE */ + + trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); + set_baseline_state(trigger_data); + +done: + spin_unlock_bh(&trigger_data->lock); + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_work(struct work_struct *work) +{ + struct led_netdev_data *trigger_data = container_of(work, struct led_netdev_data, work.work); + struct rtnl_link_stats64 *dev_stats; + unsigned new_activity; + struct rtnl_link_stats64 temp; + + if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { + /* we don't need to do timer work, just reflect link state. */ + led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); + return; + } + + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + new_activity = + ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + + ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); + + if (trigger_data->mode & MODE_LINK) { + /* base state is ON (link present) */ + /* if there's no link, we don't get this far and the LED is off */ + + /* OFF -> ON always */ + /* ON -> OFF on activity */ + if (trigger_data->led_cdev->brightness == LED_OFF) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } + } else { + /* base state is OFF */ + /* ON -> OFF always */ + /* OFF -> ON on activity */ + if (trigger_data->led_cdev->brightness == LED_FULL) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } + } + + trigger_data->last_activity = new_activity; + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static void netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return; + + spin_lock_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + trigger_data->interval = msecs_to_jiffies(50); + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + led_cdev->trigger_data = trigger_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_mode); + if (rc) + goto err_out_device_name; + rc = device_create_file(led_cdev->dev, &dev_attr_interval); + if (rc) + goto err_out_mode; + + register_netdevice_notifier(&trigger_data->notifier); + return; + +err_out_mode: + device_remove_file(led_cdev->dev, &dev_attr_mode); +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(trigger_data); +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (trigger_data) { + unregister_netdevice_notifier(&trigger_data->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_mode); + device_remove_file(led_cdev->dev, &dev_attr_interval); + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + spin_unlock_bh(&trigger_data->lock); + + kfree(trigger_data); + } +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, +}; + +static int __init netdev_trig_init(void) +{ + return led_trigger_register(&netdev_led_trigger); +} + +static void __exit netdev_trig_exit(void) +{ + led_trigger_unregister(&netdev_led_trigger); +} + +module_init(netdev_trig_init); +module_exit(netdev_trig_exit); + +MODULE_AUTHOR("Oliver Jowett "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.14/drivers/misc/owl-loader.c b/target/linux/generic/files-4.14/drivers/misc/owl-loader.c new file mode 100644 index 0000000000..f11cb2b49c --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/misc/owl-loader.c @@ -0,0 +1,246 @@ +/* + * Initialize Owl Emulation Devices + * + * Copyright (C) 2016 Christian Lamparter + * Copyright (C) 2016 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) + * need to be able to initialize the PCIe wifi device. Normally, this is done + * during the early stages of booting linux, because the necessary init code + * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup. + * However,this isn't possible for devices which have the init code for the + * Atheros chip stored on NAND. Hence, this module can be used to initialze + * the chip when the user-space is ready to extract the init code. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct owl_ctx { + struct completion eeprom_load; +}; + +#define EEPROM_FILENAME_LEN 100 + +#define AR5416_EEPROM_MAGIC 0xa55a + +static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, + size_t cal_len) +{ + void __iomem *mem; + const void *cal_end = (void *)cal_data + cal_len; + const struct { + __be16 reg; + __be16 low_val; + __be16 high_val; + } __packed *data; + u16 cmd; + u32 bar0; + bool swap_needed = false; + + if (*cal_data != AR5416_EEPROM_MAGIC) { + if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { + dev_err(&pdev->dev, "invalid calibration data\n"); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "calibration data needs swapping\n"); + swap_needed = true; + } + + dev_info(&pdev->dev, "fixup device configuration\n"); + + mem = pcim_iomap(pdev, 0, 0); + if (!mem) { + dev_err(&pdev->dev, "ioremap error\n"); + return -EINVAL; + } + + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, + pci_resource_start(pdev, 0)); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + /* set pointer to first reg address */ + for (data = (const void *) (cal_data + 3); + (const void *) data <= cal_end && data->reg != cpu_to_be16(~0); + data++) { + u32 val; + u16 reg; + + reg = data->reg; + val = data->low_val; + val |= data->high_val << 16; + + if (swap_needed) { + reg = swab16(reg); + val = swahb32(val); + } + +#ifdef CONFIG_LANTIQ + val = swab32(val); +#endif + + __raw_writel(val, mem + reg); + udelay(100); + } + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); + pcim_iounmap(pdev, mem); + + pci_disable_device(pdev); + + return 0; +} + +static void owl_fw_cb(const struct firmware *fw, void *context) +{ + struct pci_dev *pdev = (struct pci_dev *) context; + struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev); + struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct pci_bus *bus; + + complete(&ctx->eeprom_load); + + if (!fw) { + dev_err(&pdev->dev, "no eeprom data received.\n"); + goto release; + } + + /* also note that we are doing *u16 operations on the file */ + if (fw->size > sizeof(pdata->eeprom_data) || fw->size < 0x200 || + (fw->size & 1) == 1) { + dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); + goto release; + } + + if (pdata) { + memcpy(pdata->eeprom_data, fw->data, fw->size); + + /* + * eeprom has been successfully loaded - pass the data to ath9k + * but remove the eeprom_name, so it doesn't try to load it too. + */ + pdata->eeprom_name = NULL; + } + + if (ath9k_pci_fixup(pdev, (const u16 *) fw->data, fw->size)) + goto release; + + pci_lock_rescan_remove(); + bus = pdev->bus; + pci_stop_and_remove_bus_device(pdev); + /* + * the device should come back with the proper + * ProductId. But we have to initiate a rescan. + */ + pci_rescan_bus(bus); + pci_unlock_rescan_remove(); + +release: + release_firmware(fw); +} + +static const char *owl_get_eeprom_name(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct ath9k_platform_data *pdata; + char *eeprom_name; + + /* try the existing platform data first */ + pdata = dev_get_platdata(dev); + if (pdata && pdata->eeprom_name) + return pdata->eeprom_name; + + dev_dbg(dev, "using auto-generated eeprom filename\n"); + + eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); + if (!eeprom_name) + return NULL; + + /* this should match the pattern used in ath9k/init.c */ + scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", + dev_name(dev)); + + return eeprom_name; +} + +static int owl_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct owl_ctx *ctx; + const char *eeprom_name; + int err = 0; + + if (pcim_enable_device(pdev)) + return -EIO; + + pcim_pin_device(pdev); + + eeprom_name = owl_get_eeprom_name(pdev); + if (!eeprom_name) { + dev_err(&pdev->dev, "no eeprom filename found.\n"); + return -ENODEV; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + dev_err(&pdev->dev, "failed to alloc device context.\n"); + return -ENOMEM; + } + init_completion(&ctx->eeprom_load); + + pci_set_drvdata(pdev, ctx); + err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, + &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); + if (err) { + dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); + kfree(ctx); + } + return err; +} + +static void owl_remove(struct pci_dev *pdev) +{ + struct owl_ctx *ctx = pci_get_drvdata(pdev); + + if (ctx) { + wait_for_completion(&ctx->eeprom_load); + pci_set_drvdata(pdev, NULL); + kfree(ctx); + } +} + +static const struct pci_device_id owl_pci_table[] = { + { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ + { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ + { }, +}; +MODULE_DEVICE_TABLE(pci, owl_pci_table); + +static struct pci_driver owl_driver = { + .name = "owl-loader", + .id_table = owl_pci_table, + .probe = owl_probe, + .remove = owl_remove, +}; +module_pci_driver(owl_driver); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Kconfig new file mode 100644 index 0000000000..81ece43db8 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Kconfig @@ -0,0 +1,76 @@ +config MTD_SPLIT + def_bool n + help + Generic MTD split support. + +config MTD_SPLIT_SUPPORT + def_bool MTD = y + +comment "Rootfs partition parsers" + +config MTD_SPLIT_SQUASHFS_ROOT + bool "Squashfs based root partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + default n + help + This provides a parsing function which allows to detect the + offset and size of the unused portion of a rootfs partition + containing a squashfs. + +comment "Firmware partition parsers" + +config MTD_SPLIT_SEAMA_FW + bool "Seama firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_WRGG_FW + bool "WRGG firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_UIMAGE_FW + bool "uImage based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_FIT_FW + bool "FIT based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_LZMA_FW + bool "LZMA compressed kernel based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TPLINK_FW + bool "TP-Link firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TRX_FW + bool "TRX image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_BRNIMAGE_FW + bool "brnImage (brnboot image) firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_EVA_FW + bool "EVA image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_MINOR_FW + bool "Mikrotik NOR image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_JIMAGE_FW + bool "JBOOT Image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Makefile new file mode 100644 index 0000000000..206e754a18 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o +obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o +obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o +obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o +obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o +obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o +obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o +obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o +obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o +obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o +obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o +obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o +obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.c new file mode 100644 index 0000000000..b2e51dcfc6 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) "mtdsplit: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define UBI_EC_MAGIC 0x55424923 /* UBI# */ + +struct squashfs_super_block { + __le32 s_magic; + __le32 pad0[9]; + __le64 bytes_used; +}; + +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + struct squashfs_super_block sb; + size_t retlen; + int err; + + err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb); + if (err || (retlen != sizeof(sb))) { + pr_alert("error occured while reading from \"%s\"\n", + master->name); + return -EIO; + } + + if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) { + pr_alert("no squashfs found in \"%s\"\n", master->name); + return -EINVAL; + } + + retlen = le64_to_cpu(sb.bytes_used); + if (retlen <= 0) { + pr_alert("squashfs is empty in \"%s\"\n", master->name); + return -ENODEV; + } + + if (offset + retlen > master->size) { + pr_alert("squashfs has invalid size in \"%s\"\n", + master->name); + return -EINVAL; + } + + *squashfs_len = retlen; + return 0; +} +EXPORT_SYMBOL_GPL(mtd_get_squashfs_len); + +static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset) +{ + return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize; +} + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + u32 magic; + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (ret) + return ret; + + if (retlen != sizeof(magic)) + return -EIO; + + if (le32_to_cpu(magic) == SQUASHFS_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_SQUASHFS; + return 0; + } else if (magic == 0x19852003) { + if (type) + *type = MTDSPLIT_PART_TYPE_JFFS2; + return 0; + } else if (be32_to_cpu(magic) == UBI_EC_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_UBI; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + size_t offset; + int err; + + for (offset = from; offset < limit; + offset = mtd_next_eb(mtd, offset)) { + err = mtd_check_rootfs_magic(mtd, offset, type); + if (err) + continue; + + *ret_offset = offset; + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(mtd_find_rootfs_from); + diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.h b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.h new file mode 100644 index 0000000000..71d62a8956 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MTDSPLIT_H +#define _MTDSPLIT_H + +#define KERNEL_PART_NAME "kernel" +#define ROOTFS_PART_NAME "rootfs" +#define UBI_PART_NAME "ubi" + +#define ROOTFS_SPLIT_NAME "rootfs_data" + +enum mtdsplit_part_type { + MTDSPLIT_PART_TYPE_UNK = 0, + MTDSPLIT_PART_TYPE_SQUASHFS, + MTDSPLIT_PART_TYPE_JFFS2, + MTDSPLIT_PART_TYPE_UBI, +}; + +#ifdef CONFIG_MTD_SPLIT +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len); + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type); + +#else +static inline int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + return -ENODEV; +} + +static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + return -EINVAL; +} + +static inline int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + return -ENODEV; +} +#endif /* CONFIG_MTD_SPLIT */ + +#endif /* _MTDSPLIT_H */ diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_brnimage.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_brnimage.c new file mode 100644 index 0000000000..3f2d79601a --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_brnimage.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define BRNIMAGE_NR_PARTS 2 + +#define BRNIMAGE_ALIGN_BYTES 0x400 +#define BRNIMAGE_FOOTER_SIZE 12 + +#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE) +#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE) + +static int mtdsplit_parse_brnimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + uint32_t buf; + unsigned long rootfs_offset, rootfs_size, kernel_size; + size_t len; + int ret = 0; + + for (rootfs_offset = 0; rootfs_offset < master->size; + rootfs_offset += BRNIMAGE_ALIGN_BYTES) { + ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (!ret) + break; + } + + if (ret) + return ret; + + if (rootfs_offset >= master->size) + return -EINVAL; + + ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len, + (void *)&buf); + if (ret) + return ret; + + if (len != 4) + return -EIO; + + kernel_size = le32_to_cpu(buf); + + if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD)) + return -EINVAL; + + if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD)) + return -EINVAL; + + /* + * The footer must be untouched as it contains the checksum of the + * original brnImage (kernel + squashfs)! + */ + rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE; + + parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return BRNIMAGE_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_brnimage_parser = { + .owner = THIS_MODULE, + .name = "brnimage-fw", + .parse_fn = mtdsplit_parse_brnimage, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_brnimage_init(void) +{ + register_mtd_parser(&mtdsplit_brnimage_parser); + + return 0; +} + +subsys_initcall(mtdsplit_brnimage_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_eva.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_eva.c new file mode 100644 index 0000000000..746944ee2e --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_eva.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define EVA_NR_PARTS 2 +#define EVA_MAGIC 0xfeed1281 +#define EVA_FOOTER_SIZE 0x18 +#define EVA_DUMMY_SQUASHFS_SIZE 0x100 + +struct eva_image_header { + uint32_t magic; + uint32_t size; +}; + +static int mtdsplit_parse_eva(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct eva_image_header hdr; + size_t retlen; + unsigned long kernel_size, rootfs_offset; + int err; + + err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != sizeof(hdr)) + return -EIO; + + if (le32_to_cpu(hdr.magic) != EVA_MAGIC) + return -EINVAL; + + kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE; + + /* rootfs starts at the next 0x10000 boundary: */ + rootfs_offset = round_up(kernel_size, 0x10000); + + /* skip the dummy EVA squashfs partition (with wrong endianness): */ + rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE; + + if (rootfs_offset >= master->size) + return -EINVAL; + + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return EVA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_eva_parser = { + .owner = THIS_MODULE, + .name = "eva-fw", + .parse_fn = mtdsplit_parse_eva, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_eva_init(void) +{ + register_mtd_parser(&mtdsplit_eva_parser); + + return 0; +} + +subsys_initcall(mtdsplit_eva_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_fit.c new file mode 100644 index 0000000000..f356adcd4e --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_fit.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 The Linux Foundation + * Copyright (C) 2014 Gabor Juhos + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +struct fdt_header { + uint32_t magic; /* magic word FDT_MAGIC */ + uint32_t totalsize; /* total size of DT block */ + uint32_t off_dt_struct; /* offset to structure */ + uint32_t off_dt_strings; /* offset to strings */ + uint32_t off_mem_rsvmap; /* offset to memory reserve map */ + uint32_t version; /* format version */ + uint32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + uint32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + uint32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + uint32_t size_dt_struct; /* size of the structure block */ +}; + +static int +mtdsplit_fit_parse(struct mtd_info *mtd, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct fdt_header hdr; + size_t hdr_len, retlen; + size_t offset; + size_t fit_offset, fit_size; + size_t rootfs_offset, rootfs_size; + struct mtd_partition *parts; + int ret; + + hdr_len = sizeof(struct fdt_header); + + /* Parse the MTD device & search for the FIT image location */ + for(offset = 0; offset < mtd->size; offset += mtd->erasesize) { + ret = mtd_read(mtd, 0, hdr_len, &retlen, (void*) &hdr); + if (ret) { + pr_err("read error in \"%s\" at offset 0x%llx\n", + mtd->name, (unsigned long long) offset); + return ret; + } + + if (retlen != hdr_len) { + pr_err("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + /* Check the magic - see if this is a FIT image */ + if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) { + pr_debug("no valid FIT image found in \"%s\" at offset %llx\n", + mtd->name, (unsigned long long) offset); + continue; + } + + /* We found a FIT image. Let's keep going */ + break; + } + + fit_offset = offset; + fit_size = be32_to_cpu(hdr.totalsize); + + if (fit_size == 0) { + pr_err("FIT image in \"%s\" at offset %llx has null size\n", + mtd->name, (unsigned long long) fit_offset); + return -ENODEV; + } + + /* Search for the rootfs partition after the FIT image */ + ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, + &rootfs_offset, NULL); + if (ret) { + pr_info("no rootfs found after FIT image in \"%s\"\n", + mtd->name); + return ret; + } + + rootfs_size = mtd->size - rootfs_offset; + + parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = fit_offset; + parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return 2; +} + +static struct mtd_part_parser uimage_parser = { + .owner = THIS_MODULE, + .name = "fit-fw", + .parse_fn = mtdsplit_fit_parse, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_fit_init(void) +{ + register_mtd_parser(&uimage_parser); + + return 0; +} + +module_init(mtdsplit_fit_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_jimage.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_jimage.c new file mode 100644 index 0000000000..51544a794b --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_jimage.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2018 Paweł Dembicki + * + * Based on: mtdsplit_uimage.c + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE ) + +#define STAG_SIZE 16 +#define STAG_ID 0x04 +#define STAG_MAGIC 0x2B24 + +#define SCH2_SIZE 40 +#define SCH2_MAGIC 0x2124 +#define SCH2_VER 0x02 + +/* + * Jboot image header, + * all data in little endian. + */ + +struct jimage_header //stag + sch2 jboot joined headers +{ + uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id + uint8_t stag_id; // 0x04 + uint16_t stag_magic; //magic 0x2B24 + uint32_t stag_time_stamp; // timestamp calculated in jboot way + uint32_t stag_image_length; // lentgh of kernel + sch2 header + uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel + uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data + uint16_t sch2_magic; // magic 0x2124 + uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma + uint8_t sch2_version; // 0x02 for sch2 + uint32_t sch2_ram_addr; // ram entry address + uint32_t sch2_image_len; // kernel image length + uint32_t sch2_image_crc32; // kernel image crc + uint32_t sch2_start_addr; // ram start address + uint32_t sch2_rootfs_addr; // rootfs flash address + uint32_t sch2_rootfs_len; // rootfls length + uint32_t sch2_rootfs_crc32; // rootfs crc32 + uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero + uint16_t sch2_header_length; // sch2 header length: 0x28 + uint16_t sch2_cmd_line_length; // cmd line length, known zeros +}; + +static int +read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid jImage header if found + */ +static int __mtdsplit_parse_jimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t jimage_offset; + size_t jimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int jimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find jImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct jimage_header *header; + + jimage_size = 0; + + ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid jImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct jimage_header *)(buf + ret); + + jimage_size = sizeof(*header) + header->sch2_image_len + ret; + if ((offset + jimage_size) > master->size) { + pr_debug("jImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (jimage_size == 0) { + pr_debug("no jImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + jimage_offset = offset; + + if (jimage_offset == 0) { + jimage_part = 0; + rf_part = 1; + + /* find the roots after the jImage */ + ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + jimage_size = rootfs_offset - jimage_offset; + } else { + rf_part = 0; + jimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = jimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[jimage_part].name = KERNEL_PART_NAME; + parts[jimage_part].offset = jimage_offset; + parts[jimage_part].size = jimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t jimage_verify_default(u_char *buf, size_t len) +{ + struct jimage_header *header = (struct jimage_header *)buf; + + /* default sanity checks */ + if (header->stag_magic != STAG_MAGIC) { + pr_debug("invalid jImage stag header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_magic != SCH2_MAGIC) { + pr_debug("invalid jImage sch2 header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_cmark != header->stag_id) { + pr_debug("invalid jImage stag header cmark: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_id != STAG_ID) { + pr_debug("invalid jImage stag header id: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_version != SCH2_VER) { + pr_debug("invalid jImage sch2 header version: %02x\n", + header->stag_magic); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_jimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_jimage(master, pparts, data, + jimage_verify_default); +} + +static struct mtd_part_parser jimage_generic_parser = { + .owner = THIS_MODULE, + .name = "jimage-fw", + .parse_fn = mtdsplit_jimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_jimage_init(void) +{ + register_mtd_parser(&jimage_generic_parser); + + return 0; +} + +module_init(mtdsplit_jimage_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_lzma.c new file mode 100644 index 0000000000..b7f044a6dc --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_lzma.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "mtdsplit.h" + +#define LZMA_NR_PARTS 2 +#define LZMA_PROPERTIES_SIZE 5 + +struct lzma_header { + u8 props[LZMA_PROPERTIES_SIZE]; + u8 size_low[4]; + u8 size_high[4]; +}; + +static int mtdsplit_parse_lzma(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct lzma_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + u32 t; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* verify LZMA properties */ + if (hdr.props[0] >= (9 * 5 * 5)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.props[1]); + if (!is_power_of_2(t)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.size_high); + if (t) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return LZMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_lzma_parser = { + .owner = THIS_MODULE, + .name = "lzma-fw", + .parse_fn = mtdsplit_parse_lzma, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_lzma_init(void) +{ + register_mtd_parser(&mtdsplit_lzma_parser); + + return 0; +} + +subsys_initcall(mtdsplit_lzma_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_minor.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_minor.c new file mode 100644 index 0000000000..f971f0a650 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_minor.c @@ -0,0 +1,117 @@ +/* + * MTD splitter for MikroTik NOR devices + * + * Copyright (C) 2017 Thibaut VARENE + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * The rootfs is expected at erase-block boundary due to the use of + * mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header + * for two main reasons: + * - the original header uses weakly defined types (int, enum...) which can + * vary in length depending on build host (and the struct is not packed), + * and the name field can have a different total length depending on + * whether or not the yaffs code was _built_ with unicode support. + * - the only field that could be of real use here (file_size_low) contains + * invalid data in the header generated by kernel2minor, so we cannot use + * it to infer the exact position of the rootfs and do away with + * mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define YAFFS_OBJECT_TYPE_FILE 0x1 +#define YAFFS_OBJECTID_ROOT 0x1 +#define YAFFS_SUM_UNUSED 0xFFFF +#define YAFFS_NAME "kernel" + +#define MINOR_NR_PARTS 2 + +/* + * This structure is based on yaffs_obj_hdr from yaffs_guts.h + * The weak types match upstream. The fields have cpu-endianness + */ +struct minor_header { + int yaffs_type; + int yaffs_obj_id; + u16 yaffs_sum_unused; + char yaffs_name[sizeof(YAFFS_NAME)]; +}; + +static int mtdsplit_parse_minor(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct minor_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* match header */ + if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) + return -EINVAL; + + if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) + return -EINVAL; + + if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) + return -EINVAL; + + if (memcmp(hdr.yaffs_name, YAFFS_NAME, sizeof(YAFFS_NAME))) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return MINOR_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_minor_parser = { + .owner = THIS_MODULE, + .name = "minor-fw", + .parse_fn = mtdsplit_parse_minor, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_minor_init(void) +{ + register_mtd_parser(&mtdsplit_minor_parser); + + return 0; +} + +subsys_initcall(mtdsplit_minor_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_seama.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_seama.c new file mode 100644 index 0000000000..f8556e05a4 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_seama.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define SEAMA_MAGIC 0x5EA3A417 +#define SEAMA_NR_PARTS 2 +#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +struct seama_header { + __be32 magic; /* should always be SEAMA_MAGIC. */ + __be16 reserved; /* reserved for */ + __be16 metasize; /* size of the META data */ + __be32 size; /* size of the image */ + u8 md5[16]; /* digest */ +}; + +static int mtdsplit_parse_seama(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct seama_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) + return -EINVAL; + + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) + + be16_to_cpu(hdr.metasize); + if (kernel_ent_size > master->size) + return -EINVAL; + + /* Check for the rootfs right after Seama entity with a kernel. */ + err = mtd_check_rootfs_magic(master, kernel_ent_size, &type); + if (!err) { + rootfs_offset = kernel_ent_size; + } else { + /* + * On some devices firmware entity might contain both: kernel + * and rootfs. We can't determine kernel size so we just have to + * look for rootfs magic. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + } + + parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize); + parts[0].size = rootfs_offset - parts[0].offset; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[1].name = UBI_PART_NAME; + else + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return SEAMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_seama_parser = { + .owner = THIS_MODULE, + .name = "seama-fw", + .parse_fn = mtdsplit_parse_seama, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_seama_init(void) +{ + register_mtd_parser(&mtdsplit_seama_parser); + + return 0; +} + +subsys_initcall(mtdsplit_seama_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_squashfs.c new file mode 100644 index 0000000000..79e1f73bca --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_squashfs.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +static int +mtdsplit_parse_squashfs(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *part; + struct mtd_info *parent_mtd; + size_t part_offset; + size_t squashfs_len; + int err; + + err = mtd_get_squashfs_len(master, 0, &squashfs_len); + if (err) + return err; + + parent_mtd = mtdpart_get_master(master); + part_offset = mtdpart_get_offset(master); + + part = kzalloc(sizeof(*part), GFP_KERNEL); + if (!part) { + pr_alert("unable to allocate memory for \"%s\" partition\n", + ROOTFS_SPLIT_NAME); + return -ENOMEM; + } + + part->name = ROOTFS_SPLIT_NAME; + part->offset = mtd_roundup_to_eb(part_offset + squashfs_len, + parent_mtd) - part_offset; + part->size = mtd_rounddown_to_eb(master->size - part->offset, master); + + *pparts = part; + return 1; +} + +static struct mtd_part_parser mtdsplit_squashfs_parser = { + .owner = THIS_MODULE, + .name = "squashfs-split", + .parse_fn = mtdsplit_parse_squashfs, + .type = MTD_PARSER_TYPE_ROOTFS, +}; + +static int __init mtdsplit_squashfs_init(void) +{ + register_mtd_parser(&mtdsplit_squashfs_parser); + + return 0; +} + +subsys_initcall(mtdsplit_squashfs_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_tplink.c new file mode 100644 index 0000000000..c346aa8130 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_tplink.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TPLINK_NR_PARTS 2 +#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +#define MD5SUM_LEN 16 + +struct fw_v1 { + char vendor_name[24]; + char fw_version[36]; + uint32_t hw_id; /* hardware id */ + uint32_t hw_rev; /* hardware revision */ + uint32_t unk1; + uint8_t md5sum1[MD5SUM_LEN]; + uint32_t unk2; + uint8_t md5sum2[MD5SUM_LEN]; + uint32_t unk3; + uint32_t kernel_la; /* kernel load address */ + uint32_t kernel_ep; /* kernel entry point */ + uint32_t fw_length; /* total length of the firmware */ + uint32_t kernel_ofs; /* kernel data offset */ + uint32_t kernel_len; /* kernel data length */ + uint32_t rootfs_ofs; /* rootfs data offset */ + uint32_t rootfs_len; /* rootfs data length */ + uint32_t boot_ofs; /* bootloader data offset */ + uint32_t boot_len; /* bootloader data length */ + uint8_t pad[360]; +} __attribute__ ((packed)); + +struct fw_v2 { + char fw_version[48]; /* 0x04: fw version string */ + uint32_t hw_id; /* 0x34: hardware id */ + uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ + uint32_t unk1; /* 0x3c: 0x00000000 */ + uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ + uint32_t unk2; /* 0x50: 0x00000000 */ + uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ + uint32_t unk3; /* 0x64: 0xffffffff */ + + uint32_t kernel_la; /* 0x68: kernel load address */ + uint32_t kernel_ep; /* 0x6c: kernel entry point */ + uint32_t fw_length; /* 0x70: total length of the image */ + uint32_t kernel_ofs; /* 0x74: kernel data offset */ + uint32_t kernel_len; /* 0x78: kernel data length */ + uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ + uint32_t rootfs_len; /* 0x80: rootfs data length */ + uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ + uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ + uint16_t unk4; /* 0x8c: 0x55aa */ + uint8_t sver_hi; /* 0x8e */ + uint8_t sver_lo; /* 0x8f */ + uint8_t unk5; /* 0x90: magic: 0xa5 */ + uint8_t ver_hi; /* 0x91 */ + uint8_t ver_mid; /* 0x92 */ + uint8_t ver_lo; /* 0x93 */ + uint8_t pad[364]; +} __attribute__ ((packed)); + +struct tplink_fw_header { + uint32_t version; + union { + struct fw_v1 v1; + struct fw_v2 v2; + }; +}; + +static int mtdsplit_parse_tplink(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct tplink_fw_header hdr; + size_t hdr_len, retlen, kernel_size; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + switch (le32_to_cpu(hdr.version)) { + case 1: + if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs); + break; + case 2: + case 3: + if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs); + break; + default: + return -EINVAL; + } + + if (kernel_size > master->size) + return -EINVAL; + + /* Find the rootfs */ + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) { + /* + * The size in the header might cover the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, NULL); + if (err) + return err; + } + + parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return TPLINK_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_tplink_parser = { + .owner = THIS_MODULE, + .name = "tplink-fw", + .parse_fn = mtdsplit_parse_tplink, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_tplink_init(void) +{ + register_mtd_parser(&mtdsplit_tplink_parser); + + return 0; +} + +subsys_initcall(mtdsplit_tplink_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_trx.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_trx.c new file mode 100644 index 0000000000..53aebc5987 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_trx.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ + +struct trx_header { + __le32 magic; + __le32 len; + __le32 crc32; + __le32 flag_version; + __le32 offset[4]; +}; + +static int +read_trx_header(struct mtd_info *mtd, size_t offset, + struct trx_header *header) +{ + size_t header_len; + size_t retlen; + int ret; + + header_len = sizeof(*header); + ret = mtd_read(mtd, offset, header_len, &retlen, + (unsigned char *) header); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +static int +mtdsplit_parse_trx(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct trx_header hdr; + int nr_parts; + size_t offset; + size_t trx_offset; + size_t trx_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int ret; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + /* find trx image on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + trx_size = 0; + + ret = read_trx_header(master, offset, &hdr); + if (ret) + continue; + + if (hdr.magic != cpu_to_le32(TRX_MAGIC)) { + pr_debug("no valid trx header found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + + trx_size = le32_to_cpu(hdr.len); + if ((offset + trx_size) > master->size) { + pr_debug("trx image exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (trx_size == 0) { + pr_debug("no trx header found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + trx_offset = offset + hdr.offset[0]; + rootfs_offset = offset + hdr.offset[1]; + rootfs_size = master->size - rootfs_offset; + trx_size = rootfs_offset - trx_offset; + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = trx_offset; + parts[0].size = trx_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return nr_parts; + +err: + kfree(parts); + return ret; +} + +static struct mtd_part_parser trx_parser = { + .owner = THIS_MODULE, + .name = "trx-fw", + .parse_fn = mtdsplit_parse_trx, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_trx_init(void) +{ + register_mtd_parser(&trx_parser); + + return 0; +} + +module_init(mtdsplit_trx_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_uimage.c new file mode 100644 index 0000000000..bd1c723e74 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_uimage.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +/* + * uimage_header itself is only 64B, but it may be prepended with another data. + * Currently the biggest size is for Edimax devices: 20B + 64B + */ +#define MAX_HEADER_LEN 84 + +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_NMLEN 32 /* Image Name Length */ + +#define IH_OS_LINUX 5 /* Linux */ + +#define IH_TYPE_KERNEL 2 /* OS Kernel Image */ +#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image */ + +/* + * Legacy format image header, + * all data in network byte order (aka natural aka bigendian). + */ +struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +static int +read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid uImage header if found + */ +static int __mtdsplit_parse_uimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t uimage_offset; + size_t uimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int uimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find uImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct uimage_header *header; + + uimage_size = 0; + + ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid uImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct uimage_header *)(buf + ret); + + uimage_size = sizeof(*header) + be32_to_cpu(header->ih_size) + ret; + if ((offset + uimage_size) > master->size) { + pr_debug("uImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (uimage_size == 0) { + pr_debug("no uImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + uimage_offset = offset; + + if (uimage_offset == 0) { + uimage_part = 0; + rf_part = 1; + + /* find the roots after the uImage */ + ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + uimage_size = rootfs_offset - uimage_offset; + } else { + rf_part = 0; + uimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = uimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[uimage_part].name = KERNEL_PART_NAME; + parts[uimage_part].offset = uimage_offset; + parts[uimage_part].size = uimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t uimage_verify_default(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + + /* default sanity checks */ + if (be32_to_cpu(header->ih_magic) != IH_MAGIC) { + pr_debug("invalid uImage magic: %08x\n", + be32_to_cpu(header->ih_magic)); + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX) { + pr_debug("invalid uImage OS: %08x\n", + be32_to_cpu(header->ih_os)); + return -EINVAL; + } + + if (header->ih_type != IH_TYPE_KERNEL) { + pr_debug("invalid uImage type: %08x\n", + be32_to_cpu(header->ih_type)); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_uimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_default); +} + +static struct mtd_part_parser uimage_generic_parser = { + .owner = THIS_MODULE, + .name = "uimage-fw", + .parse_fn = mtdsplit_uimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +#define FW_MAGIC_WNR2000V1 0x32303031 +#define FW_MAGIC_WNR2000V3 0x32303033 +#define FW_MAGIC_WNR2000V4 0x32303034 +#define FW_MAGIC_WNR2200 0x32323030 +#define FW_MAGIC_WNR612V2 0x32303631 +#define FW_MAGIC_WNR1000V2 0x31303031 +#define FW_MAGIC_WNR1000V2_VC 0x31303030 +#define FW_MAGIC_WNDR3700 0x33373030 +#define FW_MAGIC_WNDR3700V2 0x33373031 +#define FW_MAGIC_WPN824N 0x31313030 + +static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + uint8_t expected_type = IH_TYPE_FILESYSTEM; + + switch (be32_to_cpu(header->ih_magic)) { + case FW_MAGIC_WNR612V2: + case FW_MAGIC_WNR1000V2: + case FW_MAGIC_WNR1000V2_VC: + case FW_MAGIC_WNR2000V1: + case FW_MAGIC_WNR2000V3: + case FW_MAGIC_WNR2200: + case FW_MAGIC_WNDR3700: + case FW_MAGIC_WNDR3700V2: + case FW_MAGIC_WPN824N: + break; + case FW_MAGIC_WNR2000V4: + expected_type = IH_TYPE_KERNEL; + break; + default: + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX || + header->ih_type != expected_type) + return -EINVAL; + + return 0; +} + +static int +mtdsplit_uimage_parse_netgear(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_wndr3700); +} + +static struct mtd_part_parser uimage_netgear_parser = { + .owner = THIS_MODULE, + .name = "netgear-fw", + .parse_fn = mtdsplit_uimage_parse_netgear, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Edimax + **************************************************/ + +#define FW_EDIMAX_OFFSET 20 +#define FW_MAGIC_EDIMAX 0x43535953 + +static ssize_t uimage_find_edimax(u_char *buf, size_t len) +{ + u32 *magic; + + if (len < FW_EDIMAX_OFFSET + sizeof(struct uimage_header)) { + pr_err("Buffer too small for checking Edimax header\n"); + return -ENOSPC; + } + + magic = (u32 *)buf; + if (be32_to_cpu(*magic) != FW_MAGIC_EDIMAX) + return -EINVAL; + + if (!uimage_verify_default(buf + FW_EDIMAX_OFFSET, len)) + return FW_EDIMAX_OFFSET; + + return -EINVAL; +} + +static int +mtdsplit_uimage_parse_edimax(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_find_edimax); +} + +static struct mtd_part_parser uimage_edimax_parser = { + .owner = THIS_MODULE, + .name = "edimax-fw", + .parse_fn = mtdsplit_uimage_parse_edimax, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_uimage_init(void) +{ + register_mtd_parser(&uimage_generic_parser); + register_mtd_parser(&uimage_netgear_parser); + register_mtd_parser(&uimage_edimax_parser); + + return 0; +} + +module_init(mtdsplit_uimage_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_wrgg.c b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_wrgg.c new file mode 100644 index 0000000000..16ebd51345 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/mtdsplit/mtdsplit_wrgg.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2016 Stijn Tintel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define WRGG_NR_PARTS 2 +#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ +#define WRGG03_MAGIC 0x20080321 +#define WRG_MAGIC 0x20040220 + +struct wrgg03_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + char version[16]; + char model[16]; + uint32_t flag[2]; + uint32_t reserve[2]; + char buildno[16]; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + +struct wrg_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + + +static int mtdsplit_parse_wrgg(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct wrgg03_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) { + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size); + } else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) { + kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu( + ((struct wrg_header*)&hdr)->size); + } else { + return -EINVAL; + } + + if (kernel_ent_size > master->size) + return -EINVAL; + + /* + * The size in the header covers the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + + parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return WRGG_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_wrgg_parser = { + .owner = THIS_MODULE, + .name = "wrgg-fw", + .parse_fn = mtdsplit_parse_wrgg, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_wrgg_init(void) +{ + register_mtd_parser(&mtdsplit_wrgg_parser); + + return 0; +} + +subsys_initcall(mtdsplit_wrgg_init); diff --git a/target/linux/generic/files-4.14/drivers/mtd/myloader.c b/target/linux/generic/files-4.14/drivers/mtd/myloader.c new file mode 100644 index 0000000000..7532d454ed --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/mtd/myloader.c @@ -0,0 +1,182 @@ +/* + * Parse MyLoader-style flash partition tables and produce a Linux partition + * array to match. + * + * Copyright (C) 2007-2009 Gabor Juhos + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BLOCK_LEN_MIN 0x10000 +#define PART_NAME_LEN 32 + +struct part_data { + struct mylo_partition_table tab; + char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; +}; + +static int myloader_parse_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct part_data *buf; + struct mylo_partition_table *tab; + struct mylo_partition *part; + struct mtd_partition *mtd_parts; + struct mtd_partition *mtd_part; + int num_parts; + int ret, i; + size_t retlen; + char *names; + unsigned long offset; + unsigned long blocklen; + + buf = vmalloc(sizeof(*buf)); + if (!buf) { + return -ENOMEM; + goto out; + } + tab = &buf->tab; + + blocklen = master->erasesize; + if (blocklen < BLOCK_LEN_MIN) + blocklen = BLOCK_LEN_MIN; + + offset = blocklen; + + /* Find the partition table */ + for (i = 0; i < 4; i++, offset += blocklen) { + printk(KERN_DEBUG "%s: searching for MyLoader partition table" + " at offset 0x%lx\n", master->name, offset); + + ret = mtd_read(master, offset, sizeof(*buf), &retlen, + (void *)buf); + if (ret) + goto out_free_buf; + + if (retlen != sizeof(*buf)) { + ret = -EIO; + goto out_free_buf; + } + + /* Check for Partition Table magic number */ + if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) + break; + + } + + if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { + printk(KERN_DEBUG "%s: no MyLoader partition table found\n", + master->name); + ret = 0; + goto out_free_buf; + } + + /* The MyLoader and the Partition Table is always present */ + num_parts = 2; + + /* Detect number of used partitions */ + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + num_parts++; + } + + mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + + num_parts * PART_NAME_LEN), GFP_KERNEL); + + if (!mtd_parts) { + ret = -ENOMEM; + goto out_free_buf; + } + + mtd_part = mtd_parts; + names = (char *)&mtd_parts[num_parts]; + + strncpy(names, "myloader", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = 0; + mtd_part->size = offset; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + strncpy(names, "partition_table", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = offset; + mtd_part->size = blocklen; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) + strncpy(names, buf->names[i], PART_NAME_LEN); + else + snprintf(names, PART_NAME_LEN, "partition%d", i); + + mtd_part->offset = le32_to_cpu(part->addr); + mtd_part->size = le32_to_cpu(part->size); + mtd_part->name = names; + mtd_part++; + names += PART_NAME_LEN; + } + + *pparts = mtd_parts; + ret = num_parts; + + out_free_buf: + vfree(buf); + out: + return ret; +} + +static struct mtd_part_parser myloader_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = myloader_parse_partitions, + .name = "MyLoader", +}; + +static int __init myloader_mtd_parser_init(void) +{ + register_mtd_parser(&myloader_mtd_parser); + + return 0; +} + +static void __exit myloader_mtd_parser_exit(void) +{ + deregister_mtd_parser(&myloader_mtd_parser); +} + +module_init(myloader_mtd_parser_init); +module_exit(myloader_mtd_parser_exit); + +MODULE_AUTHOR("Gabor Juhos "); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/adm6996.c b/target/linux/generic/files-4.14/drivers/net/phy/adm6996.c new file mode 100644 index 0000000000..42928bab07 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/adm6996.c @@ -0,0 +1,1241 @@ +/* + * ADM6996 switch driver + * + * swconfig interface based on ar8216.c + * + * Copyright (c) 2008 Felix Fietkau + * VLAN support Copyright (c) 2010, 2011 Peter Lebbing + * Copyright (c) 2013 Hauke Mehrtens + * Copyright (c) 2014 Matti Laakso + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/*#define DEBUG 1*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau, Peter Lebbing "); +MODULE_LICENSE("GPL"); + +static const char * const adm6996_model_name[] = +{ + NULL, + "ADM6996FC", + "ADM6996M", + "ADM6996L" +}; + +struct adm6996_mib_desc { + unsigned int offset; + const char *name; +}; + +struct adm6996_priv { + struct switch_dev dev; + void *priv; + + u8 eecs; + u8 eesk; + u8 eedi; + + enum adm6996_model model; + + bool enable_vlan; + bool vlan_enabled; /* Current hardware state */ + +#ifdef DEBUG + u16 addr; /* Debugging: register address to operate on */ +#endif + + u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ + u8 tagged_ports; + + u16 vlan_id[ADM_NUM_VLANS]; + u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ + u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ + + struct mutex mib_lock; + char buf[2048]; + + struct mutex reg_mutex; + + /* use abstraction for regops, we want to add gpio support in the future */ + u16 (*read)(struct adm6996_priv *priv, enum admreg reg); + void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val); +}; + +#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) +#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + +#define MIB_DESC(_o, _n) \ + { \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct adm6996_mib_desc adm6996_mibs[] = { + MIB_DESC(ADM_CL0, "RxPacket"), + MIB_DESC(ADM_CL6, "RxByte"), + MIB_DESC(ADM_CL12, "TxPacket"), + MIB_DESC(ADM_CL18, "TxByte"), + MIB_DESC(ADM_CL24, "Collision"), + MIB_DESC(ADM_CL30, "Error"), +}; + +#define ADM6996_MIB_RXB_ID 1 +#define ADM6996_MIB_TXB_ID 3 + +static inline u16 +r16(struct adm6996_priv *priv, enum admreg reg) +{ + return priv->read(priv, reg); +} + +static inline void +w16(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + priv->write(priv, reg, val); +} + +/* Minimum timing constants */ +#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */ +#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */ +#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */ + +static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + /* Bit bang from MSB to LSB */ + for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Output on rising edge */ + gpio_set_value(priv->eedi, (mask & buf[i])); + udelay(EEDI_SETUP_TIME); + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + u8 byte; + + /* Bit bang from MSB to LSB */ + for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { + u8 gp; + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Input on rising edge */ + gp = gpio_get_value(priv->eedi); + if (gp) + byte |= mask; + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + + *buf++ = byte; + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +/* Advance clock(s) */ +static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) +{ + int i; + for (i = 0; i < clocks; i++) { + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + } +} + +static u16 +adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) +{ + /* cmd: 01 10 T DD R RRRRRR */ + u8 bits[6] = { + 0xFF, 0xFF, 0xFF, 0xFF, + (0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), + ((reg&63)<<2) + }; + + u8 rbits[4]; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + adm6996_gpio_write(priv, 0, bits, 46); + gpio_direction_input(priv->eedi); + adm6996_gpio_adclk(priv, 2); + adm6996_gpio_read(priv, 0, rbits, 32); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + + /* EEPROM has 16-bit registers, but pumps out two registers in one request */ + return (reg & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); +} + +/* Write chip configuration register */ +/* Follow 93c66 timing and chip's min EEPROM timing requirement */ +static void +adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ + u8 bits[4] = { + (0x05 << 5) | (reg >> 3), + (reg << 5) | (u8)(val >> 11), + (u8)(val >> 3), + (u8)(val << 5) + }; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + /* Write cmd. Total 27 bits */ + adm6996_gpio_write(priv, 1, bits, 27); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + gpio_direction_input(priv->eedi); +} + +static u16 +adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1) + return -EINVAL; + + priv->enable_vlan = val->value.i; + + return 0; +}; + +static int +adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->enable_vlan; + + return 0; +}; + +#ifdef DEBUG + +static int +adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1023) + return -EINVAL; + + priv->addr = val->value.i; + + return 0; +}; + +static int +adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->addr; + + return 0; +}; + +static int +adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 65535) + return -EINVAL; + + w16(priv, priv->addr, val->value.i); + + return 0; +}; + +static int +adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = r16(priv, priv->addr); + + return 0; +}; + +#endif /* def DEBUG */ + +static int +adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_pvid port %d vlan %d\n", port, vlan); + + if (vlan > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->pvid[port] = vlan; + + return 0; +} + +static int +adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_pvid port %d\n", port); + *vlan = priv->pvid[port]; + + return 0; +} + +static int +adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i); + + if (val->value.i > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + + return 0; +}; + +static int +adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_vid port %d\n", val->port_vlan); + + val->value.i = priv->vlan_id[val->port_vlan]; + + return 0; +}; + +static int +adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + u8 tagged = priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("get_ports port_vlan %d\n", val->port_vlan); + + val->len = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + + return 0; +}; + +static int +adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 *ports = &priv->vlan_table[val->port_vlan]; + u8 *tagged = &priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("set_ports port_vlan %d ports", val->port_vlan); + + *ports = 0; + *tagged = 0; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + +#ifdef DEBUG + pr_cont(" %d%s", p->id, + ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : + "")); +#endif + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + *tagged |= (1 << p->id); + priv->tagged_ports |= (1 << p->id); + } + + *ports |= (1 << p->id); + } + +#ifdef DEBUG + pr_cont("\n"); +#endif + + return 0; +}; + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_enable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_OTBE_MASK); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg &= ~(ADM_IFNTE_MASK); + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg |= ADM_VID_CHECK_MASK; + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg |= ADM_NTTE; + reg &= ~(ADM_RVID1); + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + w16(priv, ADM_SYSC3, reg); +} + +static void +adm6996_enable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + reg |= ADM_MAC_CLONE; + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + reg = ADM_VLAN_FILT_MEMBER_MASK; + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg |= ADM_OTBE_MASK; + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg |= ADM_IFNTE_MASK; + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg &= ~(ADM_VID_CHECK_MASK); + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg &= ~(ADM_NTTE); + reg |= ADM_RVID1; + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + w16(priv, ADM_VLAN_MAP(i), 0); + } + + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + reg &= ~(ADM_MAC_CLONE); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_port_pvids(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = r16(priv, adm_portcfg[i]); + reg &= ~(ADM_PORTCFG_PVID_MASK); + reg |= ADM_PORTCFG_PVID(priv->pvid[i]); + if (priv->model == ADM6996L) { + if (priv->tagged_ports & (1 << i)) + reg |= (1 << 4); + else + reg &= ~(1 << 4); + } + w16(priv, adm_portcfg[i], reg); + } + + w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); + w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P2_PVID_VAL(priv->pvid[2]); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = ADM_P3_PVID_VAL(priv->pvid[3]); + reg |= ADM_P4_PVID_VAL(priv->pvid[4]); + w16(priv, ADM_P3_P4_PVID, reg); + reg = r16(priv, ADM_P5_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P5_PVID_VAL(priv->pvid[5]); + w16(priv, ADM_P5_PVID, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_vlan_filters(struct adm6996_priv *priv) +{ + u8 ports, tagged; + u16 vid, reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + vid = priv->vlan_id[i]; + ports = priv->vlan_table[i]; + tagged = priv->vlan_tagged[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_FILT_H(i), 0); + w16(priv, ADM_VLAN_FILT_L(i), 0); + continue; + } + + reg = ADM_VLAN_FILT_MEMBER(ports); + reg |= ADM_VLAN_FILT_TAGGED(tagged); + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } +} + +static void +adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) +{ + u8 ports; + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + ports = priv->vlan_table[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_MAP(i), 0); + continue; + } else { + reg = ADM_VLAN_FILT(ports); + w16(priv, ADM_VLAN_MAP(i), reg); + } + } +} + +static int +adm6996_hw_apply(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("hw_apply\n"); + + mutex_lock(&priv->reg_mutex); + + if (!priv->enable_vlan) { + if (priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_disable_vlan_6996l(priv); + else + adm6996_disable_vlan(priv); + priv->vlan_enabled = 0; + } + goto out; + } + + if (!priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_enable_vlan_6996l(priv); + else + adm6996_enable_vlan(priv); + priv->vlan_enabled = 1; + } + + adm6996_apply_port_pvids(priv); + if (priv->model == ADM6996L) + adm6996_apply_vlan_filters_6996l(priv); + else + adm6996_apply_vlan_filters(priv); + +out: + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +/* + * Reset the switch + * + * The ADM6996 can't do a software-initiated reset, so we just initialise the + * registers we support in this driver. + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_perform_reset (struct adm6996_priv *priv) +{ + int i; + + /* initialize port and vlan settings */ + for (i = 0; i < ADM_NUM_PORTS - 1; i++) { + w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT | + ADM_PORTCFG_PVID(0)); + } + w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); + + if (priv->model == ADM6996M || priv->model == ADM6996FC) { + /* reset all PHY ports */ + for (i = 0; i < ADM_PHY_PORTS; i++) { + w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); + } + } + + priv->enable_vlan = 0; + priv->vlan_enabled = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + priv->pvid[i] = 0; + } + + for (i = 0; i < ADM_NUM_VLANS; i++) { + priv->vlan_id[i] = i; + priv->vlan_table[i] = 0; + priv->vlan_tagged[i] = 0; + } + + if (priv->model == ADM6996M) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan(priv); + adm6996_apply_port_pvids(priv); + } else if (priv->model == ADM6996L) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan_6996l(priv); + adm6996_apply_port_pvids(priv); + } +} + +static int +adm6996_reset_switch(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("reset\n"); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +adm6996_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct adm6996_priv *priv = to_adm(dev); + + u16 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + switch (port) { + case 0: + reg = r16(priv, ADM_PS0); + break; + case 1: + reg = r16(priv, ADM_PS0); + reg = reg >> 8; + break; + case 2: + reg = r16(priv, ADM_PS1); + break; + case 3: + reg = r16(priv, ADM_PS1); + reg = reg >> 8; + break; + case 4: + reg = r16(priv, ADM_PS1); + reg = reg >> 12; + break; + case 5: + reg = r16(priv, ADM_PS2); + /* Bits 0, 1, 3 and 4. */ + reg = (reg & 3) | ((reg & 24) >> 1); + break; + default: + return -EINVAL; + } + + link->link = reg & ADM_PS_LS; + if (!link->link) + return 0; + link->aneg = true; + link->duplex = reg & ADM_PS_DS; + link->tx_flow = reg & ADM_PS_FCS; + link->rx_flow = reg & ADM_PS_FCS; + if (reg & ADM_PS_SS) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +adm6996_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + int port; + char *buf = priv->buf; + int i, len = 0; + u32 reg = 0; + + port = val->port_vlan; + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "Port %d MIB counters\n", + port); + + for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { + reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %u\n", + adm6996_mibs[i].name, + reg); + } + + mutex_unlock(&priv->mib_lock); + + val->value.s = buf; + val->len = len; + + return 0; +} + +static int +adm6996_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + struct adm6996_priv *priv = to_adm(dev); + int id; + u32 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + id = ADM6996_MIB_TXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->tx_bytes = reg; + + id = ADM6996_MIB_RXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->rx_bytes = reg; + + mutex_unlock(&priv->mib_lock); + + return 0; +} + +static struct switch_attr adm6996_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLANs", + .set = adm6996_set_enable_vlan, + .get = adm6996_get_enable_vlan, + }, +#ifdef DEBUG + { + .type = SWITCH_TYPE_INT, + .name = "addr", + .description = + "Direct register access: set register address (0 - 1023)", + .set = adm6996_set_addr, + .get = adm6996_get_addr, + }, + { + .type = SWITCH_TYPE_INT, + .name = "data", + .description = + "Direct register access: read/write to register (0 - 65535)", + .set = adm6996_set_data, + .get = adm6996_get_data, + }, +#endif /* def DEBUG */ +}; + +static struct switch_attr adm6996_port[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = adm6996_sw_get_port_mib, + }, +}; + +static struct switch_attr adm6996_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = adm6996_set_vid, + .get = adm6996_get_vid, + }, +}; + +static struct switch_dev_ops adm6996_ops = { + .attr_global = { + .attr = adm6996_globals, + .n_attr = ARRAY_SIZE(adm6996_globals), + }, + .attr_port = { + .attr = adm6996_port, + .n_attr = ARRAY_SIZE(adm6996_port), + }, + .attr_vlan = { + .attr = adm6996_vlan, + .n_attr = ARRAY_SIZE(adm6996_vlan), + }, + .get_port_pvid = adm6996_get_pvid, + .set_port_pvid = adm6996_set_pvid, + .get_vlan_ports = adm6996_get_ports, + .set_vlan_ports = adm6996_set_ports, + .apply_config = adm6996_hw_apply, + .reset_switch = adm6996_reset_switch, + .get_port_link = adm6996_get_port_link, + .get_port_stats = adm6996_get_port_stats, +}; + +static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) +{ + struct switch_dev *swdev; + u16 test, old; + + if (!priv->model) { + /* Detect type of chip */ + old = r16(priv, ADM_VID_CHECK); + test = old ^ (1 << 12); + w16(priv, ADM_VID_CHECK, test); + test ^= r16(priv, ADM_VID_CHECK); + if (test & (1 << 12)) { + /* + * Bit 12 of this register is read-only. + * This is the FC model. + */ + priv->model = ADM6996FC; + } else { + /* Bit 12 is read-write. This is the M model. */ + priv->model = ADM6996M; + w16(priv, ADM_VID_CHECK, old); + } + } + + swdev = &priv->dev; + swdev->name = (adm6996_model_name[priv->model]); + swdev->cpu_port = ADM_CPU_PORT; + swdev->ports = ADM_NUM_PORTS; + swdev->vlans = ADM_NUM_VLANS; + swdev->ops = &adm6996_ops; + swdev->alias = alias; + + /* The ADM6996L connected through GPIOs does not support any switch + status calls */ + if (priv->model == ADM6996L) { + adm6996_ops.attr_port.n_attr = 0; + adm6996_ops.get_port_link = NULL; + } + + pr_info ("%s: %s model PHY found.\n", alias, swdev->name); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + + if (priv->model == ADM6996M || priv->model == ADM6996L) { + return register_switch(swdev, netdev); + } + + return -ENODEV; +} + +static int adm6996_config_init(struct phy_device *pdev) +{ + struct adm6996_priv *priv; + int ret; + + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + + if (pdev->mdio.addr != 0) { + pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" + , pdev->attached_dev->name, pdev->mdio.addr); + return 0; + } + + priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + priv->priv = pdev; + priv->read = adm6996_read_mii_reg; + priv->write = adm6996_write_mii_reg; + + ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); + if (ret < 0) + return ret; + + pdev->priv = priv; + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int adm6996_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* Our custom registers are at PHY addresses 0-10. Claim those. */ + if (dev->mdio.addr > 10) + return 0; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; + if (reg != ADM_SIG0_VAL) + return 0; + + reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; + if (reg != ADM_SIG1_VAL) + return 0; + + dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; + + return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ + return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ + struct adm6996_priv *priv = phy_to_adm(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); +} + +static int adm6996_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver adm6996_phy_driver = { + .name = "Infineon ADM6996", + .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = adm6996_probe, + .remove = adm6996_remove, + .config_init = &adm6996_config_init, + .config_aneg = &adm6996_config_aneg, + .read_status = &adm6996_read_status, + .soft_reset = adm6996_soft_reset, +}; + +static int adm6996_gpio_probe(struct platform_device *pdev) +{ + struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; + struct adm6996_priv *priv; + int ret; + + if (!pdata) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + + priv->eecs = pdata->eecs; + priv->eedi = pdata->eedi; + priv->eesk = pdata->eesk; + + priv->model = pdata->model; + priv->read = adm6996_read_gpio_reg; + priv->write = adm6996_write_gpio_reg; + + ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); + if (ret) + return ret; + + ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int adm6996_gpio_remove(struct platform_device *pdev) +{ + struct adm6996_priv *priv = platform_get_drvdata(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); + + return 0; +} + +static struct platform_driver adm6996_gpio_driver = { + .probe = adm6996_gpio_probe, + .remove = adm6996_gpio_remove, + .driver = { + .name = "adm6996_gpio", + }, +}; + +static int __init adm6996_init(void) +{ + int err; + + phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); + err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); + if (err) + return err; + + err = platform_driver_register(&adm6996_gpio_driver); + if (err) + phy_driver_unregister(&adm6996_phy_driver); + + return err; +} + +static void __exit adm6996_exit(void) +{ + platform_driver_unregister(&adm6996_gpio_driver); + phy_driver_unregister(&adm6996_phy_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/adm6996.h b/target/linux/generic/files-4.14/drivers/net/phy/adm6996.h new file mode 100644 index 0000000000..6fd460a465 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/adm6996.h @@ -0,0 +1,186 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau + * Copyright (c) 2010,2011 Peter Lebbing + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +/* + * ADM_PHY_PORTS: Number of ports with a PHY. + * We only control ports 0 to 3, because if 4 is connected, it is most likely + * not connected to the switch but to a separate MII and MAC for the WAN port. + */ +#define ADM_PHY_PORTS 4 +#define ADM_NUM_PORTS 6 +#define ADM_CPU_PORT 5 + +#define ADM_NUM_VLANS 16 +#define ADM_VLAN_MAX_ID 4094 + +enum admreg { + ADM_EEPROM_BASE = 0x0, + ADM_P0_CFG = ADM_EEPROM_BASE + 1, + ADM_P1_CFG = ADM_EEPROM_BASE + 3, + ADM_P2_CFG = ADM_EEPROM_BASE + 5, + ADM_P3_CFG = ADM_EEPROM_BASE + 7, + ADM_P4_CFG = ADM_EEPROM_BASE + 8, + ADM_P5_CFG = ADM_EEPROM_BASE + 9, + ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, + ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, + ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, + /* Input Force No Tag Enable */ + ADM_IFNTE = ADM_EEPROM_BASE + 0x20, + ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, + ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, + ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, + /* Output Tag Bypass Enable and P2 PVID */ + ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, + ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, + ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, + ADM_EEPROM_EXT_BASE = 0x40, +#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) +#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) +#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n) + ADM_COUNTER_BASE = 0xa0, + ADM_SIG0 = ADM_COUNTER_BASE + 0, + ADM_SIG1 = ADM_COUNTER_BASE + 1, + ADM_PS0 = ADM_COUNTER_BASE + 2, + ADM_PS1 = ADM_COUNTER_BASE + 3, + ADM_PS2 = ADM_COUNTER_BASE + 4, + ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */ + ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */ + ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */ + ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */ + ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */ + ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */ +#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2) + ADM_PHY_BASE = 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define ADM_SIG0_MASK 0xffff +#define ADM_SIG0_VAL 0x1023 +#define ADM_SIG1_MASK 0xffff +#define ADM_SIG1_VAL 0x0007 + +enum { + ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ + ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ + ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ + ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ + ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ + ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ + ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ + ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ + ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ + ADM_PHYCFG_INIT = ( + ADM_PHYCFG_RST | + ADM_PHYCFG_SPEED_100 | + ADM_PHYCFG_ANEN | + ADM_PHYCFG_ANEN_RST + ) +}; + +enum { + ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ + ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ + ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ + ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ + ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ + ADM_PORTCFG_PD = (1 << 5), /* Port disable */ + ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority + * 1 = TOS based priority */ + ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ + ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ + ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ + ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ + ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ + + ADM_PORTCFG_INIT = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_AN | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_DPLX | + ADM_PORTCFG_CAM + ), + ADM_PORTCFG_CPU = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_OT | + ADM_PORTCFG_DPLX + ), +}; + +#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) +#define ADM_PORTCFG_PVID_MASK (0xf << 10) + +#define ADM_IFNTE_MASK (0x3f << 9) +#define ADM_VID_CHECK_MASK (0x3f << 6) + +#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) +#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_MASK 0xff + +#define ADM_OTBE(n) (((n) & 0x3f) << 8) +#define ADM_OTBE_MASK (0x3f << 8) + +/* ADM_SYSC0 */ +enum { + ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ + ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ +}; + +/* Tag Based VLAN in ADM_SYSC3 */ +#define ADM_MAC_CLONE BIT(4) +#define ADM_TBV BIT(5) + +static const u8 adm_portcfg[] = { + [0] = ADM_P0_CFG, + [1] = ADM_P1_CFG, + [2] = ADM_P2_CFG, + [3] = ADM_P3_CFG, + [4] = ADM_P4_CFG, + [5] = ADM_P5_CFG, +}; + +/* Fields in ADM_VLAN_FILT_L(x) */ +#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) +#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) +#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) +#define ADM_VLAN_FILT_MEMBER_MASK 0x3f +/* Fields in ADM_VLAN_FILT_H(x) */ +#define ADM_VLAN_FILT_VALID (1 << 15) +#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) + +/* Convert ports to a form for ADM6996L VLAN map */ +#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \ + ((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \ + ((ports & 0x10) << 3) | ((ports & 0x20) << 3)) + +/* Port status register */ +enum { + ADM_PS_LS = (1 << 0), /* Link status */ + ADM_PS_SS = (1 << 1), /* Speed status */ + ADM_PS_DS = (1 << 2), /* Duplex status */ + ADM_PS_FCS = (1 << 3) /* Flow control status */ +}; + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/ar8216.c b/target/linux/generic/files-4.14/drivers/net/phy/ar8216.c new file mode 100644 index 0000000000..7512ee1b43 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/ar8216.c @@ -0,0 +1,2313 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" + +extern const struct ar8xxx_chip ar8327_chip; +extern const struct ar8xxx_chip ar8337_chip; + +#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ + +#define MIB_DESC(_s , _o, _n) \ + { \ + .size = (_s), \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct ar8xxx_mib_desc ar8216_mibs[] = { + MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), +}; + +const struct ar8xxx_mib_desc ar8236_mibs[39] = { + MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), + MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), + MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), +}; + +static DEFINE_MUTEX(ar8xxx_dev_list_lock); +static LIST_HEAD(ar8xxx_dev_list); + +/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ +static int +ar8xxx_phy_poll_reset(struct mii_bus *bus) +{ + unsigned int sleep_msecs = 20; + int ret, elapsed, i; + + for (elapsed = sleep_msecs; elapsed <= 600; + elapsed += sleep_msecs) { + msleep(sleep_msecs); + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + ret = mdiobus_read(bus, i, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_RESET) + break; + if (i == AR8XXX_NUM_PHYS - 1) { + usleep_range(1000, 2000); + return 0; + } + } + } + return -ETIMEDOUT; +} + +static int +ar8xxx_phy_check_aneg(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg != AUTONEG_ENABLE) + return 0; + /* + * BMCR_ANENABLE might have been cleared + * by phy_init_hw in certain kernel versions + * therefore check for it + */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_ANENABLE) + return 0; + + dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); + ret |= BMCR_ANENABLE | BMCR_ANRESTART; + return phy_write(phydev, MII_BMCR, ret); +} + +void +ar8xxx_phy_init(struct ar8xxx_priv *priv) +{ + int i; + struct mii_bus *bus; + + bus = priv->mii_bus; + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + if (priv->chip->phy_fixup) + priv->chip->phy_fixup(priv, i); + + /* initialize the port itself */ + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + if (ar8xxx_has_gige(priv)) + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + + ar8xxx_phy_poll_reset(bus); +} + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = bus->read(bus, phy_id, regnum); + hi = bus->read(bus, phy_id, regnum + 1); + + return (hi << 16) | lo; +} + +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = val & 0xffff; + hi = (u16) (val >> 16); + + if (priv->chip->mii_lo_first) + { + bus->write(bus, phy_id, regnum, lo); + bus->write(bus, phy_id, regnum + 1, hi); + } else { + bus->write(bus, phy_id, regnum + 1, hi); + bus->write(bus, phy_id, regnum, lo); + } +} + +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 val; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + + mutex_unlock(&bus->mdio_lock); + + return val; +} + +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); + + mutex_unlock(&bus->mdio_lock); +} + +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 ret; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + ret &= ~mask; + ret |= val; + ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); + + mutex_unlock(&bus->mdio_lock); + + return ret; +} + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); + bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); + mutex_unlock(&bus->mdio_lock); +} + +static inline void +ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) +{ + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); +} + +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); +} + +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 data; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); + mutex_unlock(&bus->mdio_lock); + + return data; +} + +static int +ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + u32 t; + + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + usleep_range(1000, 2000); + cond_resched(); + } + + return -ETIMEDOUT; +} + +static int +ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) +{ + unsigned mib_func = priv->chip->mib_func; + int ret; + + lockdep_assert_held(&priv->mib_lock); + + /* Capture the hardware statistics for all ports */ + ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); + + /* Wait for the capturing to complete. */ + ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); + if (ret) + goto out; + + ret = 0; + +out: + return ret; +} + +static int +ar8xxx_mib_capture(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); +} + +static int +ar8xxx_mib_flush(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); +} + +static void +ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) +{ + unsigned int base; + u64 *mib_stats; + int i; + + WARN_ON(port >= priv->dev.ports); + + lockdep_assert_held(&priv->mib_lock); + + base = priv->chip->reg_port_stats_start + + priv->chip->reg_port_stats_length * port; + + mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; + for (i = 0; i < priv->chip->num_mibs; i++) { + const struct ar8xxx_mib_desc *mib; + u64 t; + + mib = &priv->chip->mib_decs[i]; + t = ar8xxx_read(priv, base + mib->offset); + if (mib->size == 2) { + u64 hi; + + hi = ar8xxx_read(priv, base + mib->offset + 4); + t |= hi << 32; + } + + if (flush) + mib_stats[i] = 0; + else + mib_stats[i] += t; + cond_resched(); + } +} + +static void +ar8216_read_port_link(struct ar8xxx_priv *priv, int port, + struct switch_port_link *link) +{ + u32 status; + u32 speed; + + memset(link, '\0', sizeof(*link)); + + status = priv->chip->read_port_status(priv, port); + + link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); + } else { + link->link = true; + + if (priv->get_port_link) { + int err; + + err = priv->get_port_link(port); + if (err >= 0) + link->link = !!err; + } + } + + if (!link->link) + return; + + link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); + + if (link->aneg && link->duplex && priv->chip->read_port_eee_status) + link->eee = priv->chip->read_port_eee_status(priv, port); + + speed = (status & AR8216_PORT_STATUS_SPEED) >> + AR8216_PORT_STATUS_SPEED_S; + + switch (speed) { + case AR8216_PORT_SPEED_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR8216_PORT_SPEED_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR8216_PORT_SPEED_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } +} + +static struct sk_buff * +ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv = dev->phy_ptr; + unsigned char *buf; + + if (unlikely(!priv)) + goto error; + + if (!priv->vlan) + goto send; + + if (unlikely(skb_headroom(skb) < 2)) { + if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) + goto error; + } + + buf = skb_push(skb, 2); + buf[0] = 0x10; + buf[1] = 0x80; + +send: + return skb; + +error: + dev_kfree_skb_any(skb); + return NULL; +} + +static void +ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv; + unsigned char *buf; + int port, vlan; + + priv = dev->phy_ptr; + if (!priv) + return; + + /* don't strip the header if vlan mode is disabled */ + if (!priv->vlan) + return; + + /* strip header, get vlan id */ + buf = skb->data; + skb_pull(skb, 2); + + /* check for vlan header presence */ + if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) + return; + + port = buf[0] & 0x7; + + /* no need to fix up packets coming from a tagged source */ + if (priv->vlan_tagged & (1 << port)) + return; + + /* lookup port vid from local table, the switch passes an invalid vlan id */ + vlan = priv->vlan_id[priv->pvid[port]]; + + buf[14 + 2] &= 0xf0; + buf[14 + 2] |= vlan >> 8; + buf[15 + 2] = vlan & 0xff; +} + +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + int timeout = 20; + u32 t = 0; + + while (1) { + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + if (timeout-- <= 0) + break; + + udelay(10); + cond_resched(); + } + + pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", + (unsigned int) reg, t, mask, val); + return -ETIMEDOUT; +} + +static void +ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) + return; + if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { + val &= AR8216_VTUDATA_MEMBER; + val |= AR8216_VTUDATA_VALID; + ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); + } + op |= AR8216_VTU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_VTU, op); +} + +static void +ar8216_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); +} + +static void +ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + + op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); + ar8216_vtu_op(priv, op, port_mask); +} + +static int +ar8216_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | + AR8216_ATU_ACTIVE); + + return ret; +} + +static int +ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) { + t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; + t |= AR8216_ATU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); + } + + return ret; +} + +static u32 +ar8216_read_port_status(struct ar8xxx_priv *priv, int port) +{ + return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); +} + +static void +ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 header; + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) + header = AR8216_PORT_CTRL_HEADER; + else + header = 0; + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | header | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), + AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | + AR8216_PORT_VLAN_DEFAULT_ID, + (members << AR8216_PORT_VLAN_DEST_PORTS_S) | + (ingress << AR8216_PORT_VLAN_MODE_S) | + (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +} + +static int +ar8216_hw_init(struct ar8xxx_priv *priv) +{ + if (priv->initialized) + return 0; + + ar8xxx_phy_init(priv); + + priv->initialized = true; + return 0; +} + +static void +ar8216_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8216_GCTRL_MTU, 1518 + 8 + 2); +} + +static void +ar8216_init_port(struct ar8xxx_priv *priv, int port) +{ + /* Enable port learning and tx */ + ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | + (4 << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); + + if (port == AR8216_PORT_CPU) { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_UP | + (ar8xxx_has_gige(priv) ? + AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | + AR8216_PORT_STATUS_TXMAC | + AR8216_PORT_STATUS_RXMAC | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | + AR8216_PORT_STATUS_DUPLEX); + } else { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_AUTO); + } +} + +static void +ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8216: timeout waiting for atu to become ready\n"); +} + +static void ar8216_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_func0, r1_func1, r1_func2; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); + r2 |= 0x10; + + r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; + r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8216_wait_atu_ready(priv, r2, r1_func0); + + ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); + ar8xxx_mii_write32(priv, r2, r1_func1, 0); + ar8xxx_mii_write32(priv, r2, r1_func2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + t = ar8xxx_mii_read32(priv, r2, r1_func0); + t |= AR8216_ATU_ACTIVE; + ar8xxx_mii_write32(priv, r2, r1_func0, t); + ar8216_wait_atu_ready(priv, r2, r1_func0); + + val0 = ar8xxx_mii_read32(priv, r2, r1_func0); + val1 = ar8xxx_mii_read32(priv, r2, r1_func1); + val2 = ar8xxx_mii_read32(priv, r2, r1_func2); + + *status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; + if (!*status) + break; + + i = 0; + t = AR8216_ATU_PORT0; + while (!(val2 & t) && ++i < priv->dev.ports) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; + a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; + a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; + a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; + a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; + a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; + break; + } +} + +static void +ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), + AR8236_PORT_VLAN_DEFAULT_ID, + (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), + AR8236_PORT_VLAN2_VLAN_MODE | + AR8236_PORT_VLAN2_MEMBER, + (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | + (members << AR8236_PORT_VLAN2_MEMBER_S)); +} + +static void +ar8236_init_globals(struct ar8xxx_priv *priv) +{ + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* enable cpu port to receive arp frames */ + ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, + AR8236_ATU_CTRL_RES); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, + AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +static int +ar8316_hw_init(struct ar8xxx_priv *priv) +{ + u32 val, newval; + + val = ar8xxx_read(priv, AR8316_REG_POSTRIP); + + if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + if (priv->port4_phy) { + /* value taken from Ubiquiti RouterStation Pro */ + newval = 0x81461bea; + pr_info("ar8316: Using port 4 as PHY\n"); + } else { + newval = 0x01261be2; + pr_info("ar8316: Using port 4 as switch port\n"); + } + } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { + /* value taken from AVM Fritz!Box 7390 sources */ + newval = 0x010e5b71; + } else { + /* no known value for phy interface */ + pr_err("ar8316: unsupported mii mode: %d.\n", + priv->phy->interface); + return -EINVAL; + } + + if (val == newval) + goto out; + + ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); + + if (priv->port4_phy && + priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + /* work around for phy4 rgmii mode */ + ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); + /* rx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); + /* tx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); + msleep(1000); + } + + ar8xxx_phy_init(priv); + +out: + priv->initialized = true; + return 0; +} + +static void +ar8316_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + priv->vlan = !!val->value.i; + return 0; +} + +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan; + return 0; +} + + +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan < 0 || vlan >= dev->vlans || + port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + *vlan = priv->pvid[port]; + return 0; +} + +static int +ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + ar8216_read_port_link(priv, port, link); + return 0; +} + +static int +ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports; + int i; + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + ports = priv->vlan_table[val->port_vlan]; + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + priv->vlan_tagged |= (1 << p->id); + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8216_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + for (port = 0; port < AR8216_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_RX); + + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_TX); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8216_NUM_PORTS || + priv->monitor_port >= AR8216_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_RX); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_TX); +} + +static inline u32 +ar8xxx_age_time_val(int age_time) +{ + return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; +} + +static inline void +ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) +{ + u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); + ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); +} + +int +ar8xxx_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u8 portmask[AR8X16_MAX_PORTS]; + int i, j; + + mutex_lock(&priv->reg_mutex); + /* flush all vlan translation unit entries */ + priv->chip->vtu_flush(priv); + + memset(portmask, 0, sizeof(portmask)); + if (!priv->init) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < dev->ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + chip->vtu_load_vlan(priv, priv->vlan_id[j], + priv->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < dev->ports; i++) { + if (i == AR8216_PORT_CPU) + continue; + + portmask[i] = 1 << AR8216_PORT_CPU; + portmask[AR8216_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < dev->ports; i++) { + chip->setup_port(priv, i, portmask[i]); + } + + chip->set_mirror_regs(priv); + + /* set age time */ + if (chip->reg_arl_ctrl) + ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +int +ar8xxx_sw_reset_switch(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + int i; + + mutex_lock(&priv->reg_mutex); + memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - + offsetof(struct ar8xxx_priv, vlan)); + + for (i = 0; i < AR8X16_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + /* Configure all ports */ + for (i = 0; i < dev->ports; i++) + chip->init_port(priv, i); + + priv->mirror_rx = false; + priv->mirror_tx = false; + priv->source_port = 0; + priv->monitor_port = 0; + priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; + + chip->init_globals(priv); + chip->atu_flush(priv); + + mutex_unlock(&priv->reg_mutex); + + return chip->sw_hw_apply(dev); +} + +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + unsigned int len; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + mutex_lock(&priv->mib_lock); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + memset(priv->mib_stats, '\0', len); + ret = ar8xxx_mib_flush(priv); + if (ret) + goto unlock; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_rx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_rx; + return 0; +} + +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_tx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_tx; + return 0; +} + +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->monitor_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->monitor_port; + return 0; +} + +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->source_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->source_port; + return 0; +} + +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, true); + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +static void +ar8xxx_byte_to_str(char *buf, int len, u64 byte) +{ + unsigned long b; + const char *unit; + + if (byte >= 0x40000000) { /* 1 GiB */ + b = byte * 10 / 0x40000000; + unit = "GiB"; + } else if (byte >= 0x100000) { /* 1 MiB */ + b = byte * 10 / 0x100000; + unit = "MiB"; + } else if (byte >= 0x400) { /* 1 KiB */ + b = byte * 10 / 0x400; + unit = "KiB"; + } else { + b = byte; + unit = "Byte"; + } + if (strcmp(unit, "Byte")) + snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); + else + snprintf(buf, len, "%lu %s", b, unit); +} + +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u64 *mib_stats, mib_data; + unsigned int port; + int ret; + char *buf = priv->buf; + char buf1[64]; + const char *mib_name; + int i, len = 0; + bool mib_stats_empty = true; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, false); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "MIB counters\n"); + + mib_stats = &priv->mib_stats[port * chip->num_mibs]; + for (i = 0; i < chip->num_mibs; i++) { + mib_name = chip->mib_decs[i].name; + mib_data = mib_stats[i]; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %llu\n", mib_name, mib_data); + if ((!strcmp(mib_name, "TxByte") || + !strcmp(mib_name, "RxGoodByte")) && + mib_data >= 1024) { + ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); + --len; /* discard newline at the end of buf */ + len += snprintf(buf + len, sizeof(priv->buf) - len, + " (%s)\n", buf1); + } + if (mib_stats_empty && mib_data) + mib_stats_empty = false; + } + + if (mib_stats_empty) + len = snprintf(buf, sizeof(priv->buf), "No MIB data"); + + val->value.s = buf; + val->len = len; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int age_time = val->value.i; + u32 age_time_val; + + if (age_time < 0) + return -EINVAL; + + age_time_val = ar8xxx_age_time_val(age_time); + if (age_time_val == 0 || age_time_val > 0xffff) + return -EINVAL; + + priv->arl_age_time = age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->arl_age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct mii_bus *bus = priv->mii_bus; + const struct ar8xxx_chip *chip = priv->chip; + char *buf = priv->arl_buf; + int i, j, k, len = 0; + struct arl_entry *a, *a1; + u32 status; + + if (!chip->get_arl_entry) + return -EOPNOTSUPP; + + mutex_lock(&priv->reg_mutex); + mutex_lock(&bus->mdio_lock); + + chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); + + for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { + a = &priv->arl_table[i]; + duplicate: + chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); + + if (!status) + break; + + /* avoid duplicates + * ARL table can include multiple valid entries + * per MAC, just with differing status codes + */ + for (j = 0; j < i; ++j) { + a1 = &priv->arl_table[j]; + if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) + goto duplicate; + } + } + + mutex_unlock(&bus->mdio_lock); + + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "address resolution table\n"); + + if (i == AR8XXX_NUM_ARL_RECORDS) + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Too many entries found, displaying the first %d only!\n", + AR8XXX_NUM_ARL_RECORDS); + + for (j = 0; j < priv->dev.ports; ++j) { + for (k = 0; k < i; ++k) { + a = &priv->arl_table[k]; + if (a->port != j) + continue; + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + j, + a->mac[5], a->mac[4], a->mac[3], + a->mac[2], a->mac[1], a->mac[0]); + } + } + + val->value.s = buf; + val->len = len; + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int ret; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush(priv); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port, ret; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush_port(priv, port); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static const struct switch_attr ar8xxx_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_vlan[1] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = ar8xxx_sw_set_vid, + .get = ar8xxx_sw_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar8xxx_sw_ops = { + .attr_global = { + .attr = ar8xxx_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), + }, + .attr_port = { + .attr = ar8xxx_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8xxx_sw_get_ports, + .set_vlan_ports = ar8xxx_sw_set_ports, + .apply_config = ar8xxx_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +static const struct ar8xxx_chip ar8216_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x19000, + .reg_port_stats_length = 0xa0, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8216", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8216_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8216_mibs), + .mib_decs = ar8216_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8236_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8236", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8236_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8236_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8316_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8316", + .ports = AR8216_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8316_hw_init, + .init_globals = ar8316_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static int +ar8xxx_id_chip(struct ar8xxx_priv *priv) +{ + u32 val; + u16 id; + int i; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { + u16 t; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + if (t != id) + return -ENODEV; + } + + priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; + priv->chip_rev = (id & AR8216_CTRL_REVISION); + + switch (priv->chip_ver) { + case AR8XXX_VER_AR8216: + priv->chip = &ar8216_chip; + break; + case AR8XXX_VER_AR8236: + priv->chip = &ar8236_chip; + break; + case AR8XXX_VER_AR8316: + priv->chip = &ar8316_chip; + break; + case AR8XXX_VER_AR8327: + priv->chip = &ar8327_chip; + break; + case AR8XXX_VER_AR8337: + priv->chip = &ar8337_chip; + break; + default: + pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", + priv->chip_ver, priv->chip_rev); + + return -ENODEV; + } + + return 0; +} + +static void +ar8xxx_mib_work_func(struct work_struct *work) +{ + struct ar8xxx_priv *priv; + int err; + + priv = container_of(work, struct ar8xxx_priv, mib_work.work); + + mutex_lock(&priv->mib_lock); + + err = ar8xxx_mib_capture(priv); + if (err) + goto next_port; + + ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); + +next_port: + priv->mib_next_port++; + if (priv->mib_next_port >= priv->dev.ports) + priv->mib_next_port = 0; + + mutex_unlock(&priv->mib_lock); + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static int +ar8xxx_mib_init(struct ar8xxx_priv *priv) +{ + unsigned int len; + + if (!ar8xxx_has_mib_counters(priv)) + return 0; + + BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + priv->mib_stats = kzalloc(len, GFP_KERNEL); + + if (!priv->mib_stats) + return -ENOMEM; + + return 0; +} + +static void +ar8xxx_mib_start(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static void +ar8xxx_mib_stop(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + cancel_delayed_work_sync(&priv->mib_work); +} + +static struct ar8xxx_priv * +ar8xxx_create(void) +{ + struct ar8xxx_priv *priv; + + priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); + + return priv; +} + +static void +ar8xxx_free(struct ar8xxx_priv *priv) +{ + if (priv->chip && priv->chip->cleanup) + priv->chip->cleanup(priv); + + kfree(priv->chip_data); + kfree(priv->mib_stats); + kfree(priv); +} + +static int +ar8xxx_probe_switch(struct ar8xxx_priv *priv) +{ + const struct ar8xxx_chip *chip; + struct switch_dev *swdev; + int ret; + + ret = ar8xxx_id_chip(priv); + if (ret) + return ret; + + chip = priv->chip; + + swdev = &priv->dev; + swdev->cpu_port = AR8216_PORT_CPU; + swdev->name = chip->name; + swdev->vlans = chip->vlans; + swdev->ports = chip->ports; + swdev->ops = chip->swops; + + ret = ar8xxx_mib_init(priv); + if (ret) + return ret; + + return 0; +} + +static int +ar8xxx_start(struct ar8xxx_priv *priv) +{ + int ret; + + priv->init = true; + + ret = priv->chip->hw_init(priv); + if (ret) + return ret; + + ret = ar8xxx_sw_reset_switch(&priv->dev); + if (ret) + return ret; + + priv->init = false; + + ar8xxx_mib_start(priv); + + return 0; +} + +static int +ar8xxx_phy_config_init(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct net_device *dev = phydev->attached_dev; + int ret; + + if (WARN_ON(!priv)) + return -ENODEV; + + if (priv->chip->config_at_probe) + return ar8xxx_phy_check_aneg(phydev); + + priv->phy = phydev; + + if (phydev->mdio.addr != 0) { + if (chip_is_ar8316(priv)) { + /* switch device has been initialized, reinit */ + priv->dev.ports = (AR8216_NUM_PORTS - 1); + priv->initialized = false; + priv->port4_phy = true; + ar8316_hw_init(priv); + return 0; + } + + return 0; + } + + ret = ar8xxx_start(priv); + if (ret) + return ret; + + /* VID fixup only needed on ar8216 */ + if (chip_is_ar8216(priv)) { + dev->phy_ptr = priv; + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = ar8216_mangle_rx; + dev->eth_mangle_tx = ar8216_mangle_tx; + } + + return 0; +} + +static bool +ar8xxx_check_link_states(struct ar8xxx_priv *priv) +{ + bool link_new, changed = false; + u32 status; + int i; + + mutex_lock(&priv->reg_mutex); + + for (i = 0; i < priv->dev.ports; i++) { + status = priv->chip->read_port_status(priv, i); + link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); + if (link_new == priv->link_up[i]) + continue; + + priv->link_up[i] = link_new; + changed = true; + /* flush ARL entries for this port if it went down*/ + if (!link_new) + priv->chip->atu_flush_port(priv, i); + dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", + i, link_new ? "up" : "down"); + } + + mutex_unlock(&priv->reg_mutex); + + return changed; +} + +static int +ar8xxx_phy_read_status(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct switch_port_link link; + + /* check for switch port link changes */ + if (phydev->state == PHY_CHANGELINK) + ar8xxx_check_link_states(priv); + + if (phydev->mdio.addr != 0) + return genphy_read_status(phydev); + + ar8216_read_port_link(priv, phydev->mdio.addr, &link); + phydev->link = !!link.link; + if (!phydev->link) + return 0; + + switch (link.speed) { + case SWITCH_PORT_SPEED_10: + phydev->speed = SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + phydev->speed = SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + default: + phydev->speed = 0; + } + phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int +ar8xxx_phy_config_aneg(struct phy_device *phydev) +{ + if (phydev->mdio.addr == 0) + return 0; + + return genphy_config_aneg(phydev); +} + +static const u32 ar8xxx_phy_ids[] = { + 0x004dd033, + 0x004dd034, /* AR8327 */ + 0x004dd036, /* AR8337 */ + 0x004dd041, + 0x004dd042, + 0x004dd043, /* AR8236 */ +}; + +static bool +ar8xxx_phy_match(u32 phy_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) + if (phy_id == ar8xxx_phy_ids[i]) + return true; + + return false; +} + +static bool +ar8xxx_is_possible(struct mii_bus *bus) +{ + unsigned int i, found_phys = 0; + + for (i = 0; i < 5; i++) { + u32 phy_id; + + phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; + phy_id |= mdiobus_read(bus, i, MII_PHYSID2); + if (ar8xxx_phy_match(phy_id)) { + found_phys++; + } else if (phy_id) { + pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", + dev_name(&bus->dev), i, phy_id); + } + } + return !!found_phys; +} + +static int +ar8xxx_phy_probe(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv; + struct switch_dev *swdev; + int ret; + + /* skip PHYs at unused adresses */ + if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4) + return -ENODEV; + + if (!ar8xxx_is_possible(phydev->mdio.bus)) + return -ENODEV; + + mutex_lock(&ar8xxx_dev_list_lock); + list_for_each_entry(priv, &ar8xxx_dev_list, list) + if (priv->mii_bus == phydev->mdio.bus) + goto found; + + priv = ar8xxx_create(); + if (priv == NULL) { + ret = -ENOMEM; + goto unlock; + } + + priv->mii_bus = phydev->mdio.bus; + + ret = ar8xxx_probe_switch(priv); + if (ret) + goto free_priv; + + swdev = &priv->dev; + swdev->alias = dev_name(&priv->mii_bus->dev); + ret = register_switch(swdev, NULL); + if (ret) + goto free_priv; + + pr_info("%s: %s rev. %u switch registered on %s\n", + swdev->devname, swdev->name, priv->chip_rev, + dev_name(&priv->mii_bus->dev)); + + list_add(&priv->list, &ar8xxx_dev_list); + +found: + priv->use_count++; + + if (phydev->mdio.addr == 0) { + if (ar8xxx_has_gige(priv)) { + phydev->supported = SUPPORTED_1000baseT_Full; + phydev->advertising = ADVERTISED_1000baseT_Full; + } else { + phydev->supported = SUPPORTED_100baseT_Full; + phydev->advertising = ADVERTISED_100baseT_Full; + } + + if (priv->chip->config_at_probe) { + priv->phy = phydev; + + ret = ar8xxx_start(priv); + if (ret) + goto err_unregister_switch; + } + } else { + if (ar8xxx_has_gige(priv)) { + phydev->supported |= SUPPORTED_1000baseT_Full; + phydev->advertising |= ADVERTISED_1000baseT_Full; + } + } + + phydev->priv = priv; + + mutex_unlock(&ar8xxx_dev_list_lock); + + return 0; + +err_unregister_switch: + if (--priv->use_count) + goto unlock; + + unregister_switch(&priv->dev); + +free_priv: + ar8xxx_free(priv); +unlock: + mutex_unlock(&ar8xxx_dev_list_lock); + return ret; +} + +static void +ar8xxx_phy_detach(struct phy_device *phydev) +{ + struct net_device *dev = phydev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; +} + +static void +ar8xxx_phy_remove(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + + if (WARN_ON(!priv)) + return; + + phydev->priv = NULL; + + mutex_lock(&ar8xxx_dev_list_lock); + + if (--priv->use_count > 0) { + mutex_unlock(&ar8xxx_dev_list_lock); + return; + } + + list_del(&priv->list); + mutex_unlock(&ar8xxx_dev_list_lock); + + unregister_switch(&priv->dev); + ar8xxx_mib_stop(priv); + ar8xxx_free(priv); +} + +static int +ar8xxx_phy_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver ar8xxx_phy_driver[] = { + { + .phy_id = 0x004d0000, + .name = "Atheros AR8216/AR8236/AR8316", + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = ar8xxx_phy_probe, + .remove = ar8xxx_phy_remove, + .detach = ar8xxx_phy_detach, + .config_init = ar8xxx_phy_config_init, + .config_aneg = ar8xxx_phy_config_aneg, + .read_status = ar8xxx_phy_read_status, + .soft_reset = ar8xxx_phy_soft_reset, + } +}; + +module_phy_driver(ar8xxx_phy_driver); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/ar8216.h b/target/linux/generic/files-4.14/drivers/net/phy/ar8216.h new file mode 100644 index 0000000000..ba0e0ddccd --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/ar8216.h @@ -0,0 +1,644 @@ +/* + * ar8216.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8216_H +#define __AR8216_H + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) + +#define AR8XXX_CAP_GIGE BIT(0) +#define AR8XXX_CAP_MIB_COUNTERS BIT(1) + +#define AR8XXX_NUM_PHYS 5 +#define AR8216_PORT_CPU 0 +#define AR8216_NUM_PORTS 6 +#define AR8216_NUM_VLANS 16 +#define AR8316_NUM_VLANS 4096 + +/* size of the vlan table */ +#define AR8X16_MAX_VLANS 128 +#define AR8X16_PROBE_RETRIES 10 +#define AR8X16_MAX_PORTS 8 + +#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7 +#define AR8XXX_DEFAULT_ARL_AGE_TIME 300 + +/* Atheros specific MII registers */ +#define MII_ATH_MMD_ADDR 0x0d +#define MII_ATH_MMD_DATA 0x0e +#define MII_ATH_DBG_ADDR 0x1d +#define MII_ATH_DBG_DATA 0x1e + +#define AR8216_REG_CTRL 0x0000 +#define AR8216_CTRL_REVISION BITS(0, 8) +#define AR8216_CTRL_REVISION_S 0 +#define AR8216_CTRL_VERSION BITS(8, 8) +#define AR8216_CTRL_VERSION_S 8 +#define AR8216_CTRL_RESET BIT(31) + +#define AR8216_REG_FLOOD_MASK 0x002C +#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) +#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) +#define AR8236_FM_CPU_BROADCAST_EN BIT(26) +#define AR8236_FM_CPU_BCAST_FWD_EN BIT(25) + +#define AR8216_REG_GLOBAL_CTRL 0x0030 +#define AR8216_GCTRL_MTU BITS(0, 11) +#define AR8236_GCTRL_MTU BITS(0, 14) +#define AR8316_GCTRL_MTU BITS(0, 14) + +#define AR8216_REG_VTU 0x0040 +#define AR8216_VTU_OP BITS(0, 3) +#define AR8216_VTU_OP_NOOP 0x0 +#define AR8216_VTU_OP_FLUSH 0x1 +#define AR8216_VTU_OP_LOAD 0x2 +#define AR8216_VTU_OP_PURGE 0x3 +#define AR8216_VTU_OP_REMOVE_PORT 0x4 +#define AR8216_VTU_ACTIVE BIT(3) +#define AR8216_VTU_FULL BIT(4) +#define AR8216_VTU_PORT BITS(8, 4) +#define AR8216_VTU_PORT_S 8 +#define AR8216_VTU_VID BITS(16, 12) +#define AR8216_VTU_VID_S 16 +#define AR8216_VTU_PRIO BITS(28, 3) +#define AR8216_VTU_PRIO_S 28 +#define AR8216_VTU_PRIO_EN BIT(31) + +#define AR8216_REG_VTU_DATA 0x0044 +#define AR8216_VTUDATA_MEMBER BITS(0, 10) +#define AR8236_VTUDATA_MEMBER BITS(0, 7) +#define AR8216_VTUDATA_VALID BIT(11) + +#define AR8216_REG_ATU_FUNC0 0x0050 +#define AR8216_ATU_OP BITS(0, 3) +#define AR8216_ATU_OP_NOOP 0x0 +#define AR8216_ATU_OP_FLUSH 0x1 +#define AR8216_ATU_OP_LOAD 0x2 +#define AR8216_ATU_OP_PURGE 0x3 +#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4 +#define AR8216_ATU_OP_FLUSH_PORT 0x5 +#define AR8216_ATU_OP_GET_NEXT 0x6 +#define AR8216_ATU_ACTIVE BIT(3) +#define AR8216_ATU_PORT_NUM BITS(8, 4) +#define AR8216_ATU_PORT_NUM_S 8 +#define AR8216_ATU_FULL_VIO BIT(12) +#define AR8216_ATU_ADDR5 BITS(16, 8) +#define AR8216_ATU_ADDR5_S 16 +#define AR8216_ATU_ADDR4 BITS(24, 8) +#define AR8216_ATU_ADDR4_S 24 + +#define AR8216_REG_ATU_FUNC1 0x0054 +#define AR8216_ATU_ADDR3 BITS(0, 8) +#define AR8216_ATU_ADDR3_S 0 +#define AR8216_ATU_ADDR2 BITS(8, 8) +#define AR8216_ATU_ADDR2_S 8 +#define AR8216_ATU_ADDR1 BITS(16, 8) +#define AR8216_ATU_ADDR1_S 16 +#define AR8216_ATU_ADDR0 BITS(24, 8) +#define AR8216_ATU_ADDR0_S 24 + +#define AR8216_REG_ATU_FUNC2 0x0058 +#define AR8216_ATU_PORTS BITS(0, 6) +#define AR8216_ATU_PORT0 BIT(0) +#define AR8216_ATU_PORT1 BIT(1) +#define AR8216_ATU_PORT2 BIT(2) +#define AR8216_ATU_PORT3 BIT(3) +#define AR8216_ATU_PORT4 BIT(4) +#define AR8216_ATU_PORT5 BIT(5) +#define AR8216_ATU_STATUS BITS(16, 4) +#define AR8216_ATU_STATUS_S 16 + +#define AR8216_REG_ATU_CTRL 0x005C +#define AR8216_ATU_CTRL_AGE_EN BIT(17) +#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) +#define AR8216_ATU_CTRL_AGE_TIME_S 0 +#define AR8236_ATU_CTRL_RES BIT(20) + +#define AR8216_REG_MIB_FUNC 0x0080 +#define AR8216_MIB_TIMER BITS(0, 16) +#define AR8216_MIB_AT_HALF_EN BIT(16) +#define AR8216_MIB_BUSY BIT(17) +#define AR8216_MIB_FUNC BITS(24, 3) +#define AR8216_MIB_FUNC_S 24 +#define AR8216_MIB_FUNC_NO_OP 0x0 +#define AR8216_MIB_FUNC_FLUSH 0x1 +#define AR8216_MIB_FUNC_CAPTURE 0x3 +#define AR8236_MIB_EN BIT(30) + +#define AR8216_REG_GLOBAL_CPUPORT 0x0078 +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 + +#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) +#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) +#define AR8216_PORT_STATUS_SPEED BITS(0,2) +#define AR8216_PORT_STATUS_SPEED_S 0 +#define AR8216_PORT_STATUS_TXMAC BIT(2) +#define AR8216_PORT_STATUS_RXMAC BIT(3) +#define AR8216_PORT_STATUS_TXFLOW BIT(4) +#define AR8216_PORT_STATUS_RXFLOW BIT(5) +#define AR8216_PORT_STATUS_DUPLEX BIT(6) +#define AR8216_PORT_STATUS_LINK_UP BIT(8) +#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) +#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) +#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12) + +#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) + +/* port forwarding state */ +#define AR8216_PORT_CTRL_STATE BITS(0, 3) +#define AR8216_PORT_CTRL_STATE_S 0 + +#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) + +/* egress 802.1q mode */ +#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) +#define AR8216_PORT_CTRL_VLAN_MODE_S 8 + +#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR8216_PORT_CTRL_HEADER BIT(11) +#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) +#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR8216_PORT_CTRL_LEARN BIT(14) +#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) +#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) + +#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) +#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 + +#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) +#define AR8216_PORT_VLAN_DEST_PORTS_S 16 + +/* bit0 added to the priority field of egress frames */ +#define AR8216_PORT_VLAN_TX_PRIO BIT(27) + +/* port default priority */ +#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) +#define AR8216_PORT_VLAN_PRIORITY_S 28 + +/* ingress 802.1q mode */ +#define AR8216_PORT_VLAN_MODE BITS(30, 2) +#define AR8216_PORT_VLAN_MODE_S 30 + +#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) +#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) + +#define AR8216_STATS_RXBROAD 0x00 +#define AR8216_STATS_RXPAUSE 0x04 +#define AR8216_STATS_RXMULTI 0x08 +#define AR8216_STATS_RXFCSERR 0x0c +#define AR8216_STATS_RXALIGNERR 0x10 +#define AR8216_STATS_RXRUNT 0x14 +#define AR8216_STATS_RXFRAGMENT 0x18 +#define AR8216_STATS_RX64BYTE 0x1c +#define AR8216_STATS_RX128BYTE 0x20 +#define AR8216_STATS_RX256BYTE 0x24 +#define AR8216_STATS_RX512BYTE 0x28 +#define AR8216_STATS_RX1024BYTE 0x2c +#define AR8216_STATS_RXMAXBYTE 0x30 +#define AR8216_STATS_RXTOOLONG 0x34 +#define AR8216_STATS_RXGOODBYTE 0x38 +#define AR8216_STATS_RXBADBYTE 0x40 +#define AR8216_STATS_RXOVERFLOW 0x48 +#define AR8216_STATS_FILTERED 0x4c +#define AR8216_STATS_TXBROAD 0x50 +#define AR8216_STATS_TXPAUSE 0x54 +#define AR8216_STATS_TXMULTI 0x58 +#define AR8216_STATS_TXUNDERRUN 0x5c +#define AR8216_STATS_TX64BYTE 0x60 +#define AR8216_STATS_TX128BYTE 0x64 +#define AR8216_STATS_TX256BYTE 0x68 +#define AR8216_STATS_TX512BYTE 0x6c +#define AR8216_STATS_TX1024BYTE 0x70 +#define AR8216_STATS_TXMAXBYTE 0x74 +#define AR8216_STATS_TXOVERSIZE 0x78 +#define AR8216_STATS_TXBYTE 0x7c +#define AR8216_STATS_TXCOLLISION 0x84 +#define AR8216_STATS_TXABORTCOL 0x88 +#define AR8216_STATS_TXMULTICOL 0x8c +#define AR8216_STATS_TXSINGLECOL 0x90 +#define AR8216_STATS_TXEXCDEFER 0x94 +#define AR8216_STATS_TXDEFER 0x98 +#define AR8216_STATS_TXLATECOL 0x9c + +#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) +#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) +#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 +#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) +#define AR8236_PORT_VLAN_PRIORITY_S 28 + +#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) +#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) +#define AR8236_PORT_VLAN2_MEMBER_S 16 +#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) +#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) +#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 + +#define AR8236_STATS_RXBROAD 0x00 +#define AR8236_STATS_RXPAUSE 0x04 +#define AR8236_STATS_RXMULTI 0x08 +#define AR8236_STATS_RXFCSERR 0x0c +#define AR8236_STATS_RXALIGNERR 0x10 +#define AR8236_STATS_RXRUNT 0x14 +#define AR8236_STATS_RXFRAGMENT 0x18 +#define AR8236_STATS_RX64BYTE 0x1c +#define AR8236_STATS_RX128BYTE 0x20 +#define AR8236_STATS_RX256BYTE 0x24 +#define AR8236_STATS_RX512BYTE 0x28 +#define AR8236_STATS_RX1024BYTE 0x2c +#define AR8236_STATS_RX1518BYTE 0x30 +#define AR8236_STATS_RXMAXBYTE 0x34 +#define AR8236_STATS_RXTOOLONG 0x38 +#define AR8236_STATS_RXGOODBYTE 0x3c +#define AR8236_STATS_RXBADBYTE 0x44 +#define AR8236_STATS_RXOVERFLOW 0x4c +#define AR8236_STATS_FILTERED 0x50 +#define AR8236_STATS_TXBROAD 0x54 +#define AR8236_STATS_TXPAUSE 0x58 +#define AR8236_STATS_TXMULTI 0x5c +#define AR8236_STATS_TXUNDERRUN 0x60 +#define AR8236_STATS_TX64BYTE 0x64 +#define AR8236_STATS_TX128BYTE 0x68 +#define AR8236_STATS_TX256BYTE 0x6c +#define AR8236_STATS_TX512BYTE 0x70 +#define AR8236_STATS_TX1024BYTE 0x74 +#define AR8236_STATS_TX1518BYTE 0x78 +#define AR8236_STATS_TXMAXBYTE 0x7c +#define AR8236_STATS_TXOVERSIZE 0x80 +#define AR8236_STATS_TXBYTE 0x84 +#define AR8236_STATS_TXCOLLISION 0x8c +#define AR8236_STATS_TXABORTCOL 0x90 +#define AR8236_STATS_TXMULTICOL 0x94 +#define AR8236_STATS_TXSINGLECOL 0x98 +#define AR8236_STATS_TXEXCDEFER 0x9c +#define AR8236_STATS_TXDEFER 0xa0 +#define AR8236_STATS_TXLATECOL 0xa4 + +#define AR8316_REG_POSTRIP 0x0008 +#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) +#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) +#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) +#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) +#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) +#define AR8316_POSTRIP_RTL_MODE BIT(5) +#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) +#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) +#define AR8316_POSTRIP_SERDES_EN BIT(8) +#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) +#define AR8316_POSTRIP_GATE_25M_EN BIT(10) +#define AR8316_POSTRIP_SEL_CLK25M BIT(11) +#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) +#define AR8316_POSTRIP_DBG_MODE_I BIT(13) +#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) +#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) +#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) +#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) +#define AR8316_POSTRIP_MAN_EN BIT(18) +#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) +#define AR8316_POSTRIP_LPW_EXIT BIT(20) +#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) +#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) +#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) +#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) +#define AR8316_POSTRIP_SPI_EN BIT(25) +#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) +#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) + +/* port speed */ +enum { + AR8216_PORT_SPEED_10M = 0, + AR8216_PORT_SPEED_100M = 1, + AR8216_PORT_SPEED_1000M = 2, + AR8216_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + AR8216_IN_PORT_ONLY = 0, + AR8216_IN_PORT_FALLBACK = 1, + AR8216_IN_VLAN_ONLY = 2, + AR8216_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + AR8216_OUT_KEEP = 0, + AR8216_OUT_STRIP_VLAN = 1, + AR8216_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + AR8216_PORT_STATE_DISABLED = 0, + AR8216_PORT_STATE_BLOCK = 1, + AR8216_PORT_STATE_LISTEN = 2, + AR8216_PORT_STATE_LEARN = 3, + AR8216_PORT_STATE_FORWARD = 4 +}; + +enum { + AR8XXX_VER_AR8216 = 0x01, + AR8XXX_VER_AR8236 = 0x03, + AR8XXX_VER_AR8316 = 0x10, + AR8XXX_VER_AR8327 = 0x12, + AR8XXX_VER_AR8337 = 0x13, +}; + +#define AR8XXX_NUM_ARL_RECORDS 100 + +enum arl_op { + AR8XXX_ARL_INITIALIZE, + AR8XXX_ARL_GET_NEXT +}; + +struct arl_entry { + u8 port; + u8 mac[6]; +}; + +struct ar8xxx_priv; + +struct ar8xxx_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +struct ar8xxx_chip { + unsigned long caps; + bool config_at_probe; + bool mii_lo_first; + + /* parameters to calculate REG_PORT_STATS_BASE */ + unsigned reg_port_stats_start; + unsigned reg_port_stats_length; + + unsigned reg_arl_ctrl; + + int (*hw_init)(struct ar8xxx_priv *priv); + void (*cleanup)(struct ar8xxx_priv *priv); + + const char *name; + int vlans; + int ports; + const struct switch_dev_ops *swops; + + void (*init_globals)(struct ar8xxx_priv *priv); + void (*init_port)(struct ar8xxx_priv *priv, int port); + void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members); + u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); + u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port); + int (*atu_flush)(struct ar8xxx_priv *priv); + int (*atu_flush_port)(struct ar8xxx_priv *priv, int port); + void (*vtu_flush)(struct ar8xxx_priv *priv); + void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); + void (*phy_fixup)(struct ar8xxx_priv *priv, int phy); + void (*set_mirror_regs)(struct ar8xxx_priv *priv); + void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a, + u32 *status, enum arl_op op); + int (*sw_hw_apply)(struct switch_dev *dev); + + const struct ar8xxx_mib_desc *mib_decs; + unsigned num_mibs; + unsigned mib_func; +}; + +struct ar8xxx_priv { + struct switch_dev dev; + struct mii_bus *mii_bus; + struct phy_device *phy; + + int (*get_port_link)(unsigned port); + + const struct net_device_ops *ndo_old; + struct net_device_ops ndo; + struct mutex reg_mutex; + u8 chip_ver; + u8 chip_rev; + const struct ar8xxx_chip *chip; + void *chip_data; + bool initialized; + bool port4_phy; + char buf[2048]; + struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS]; + char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256]; + bool link_up[AR8X16_MAX_PORTS]; + + bool init; + + struct mutex mib_lock; + struct delayed_work mib_work; + int mib_next_port; + u64 *mib_stats; + + struct list_head list; + unsigned int use_count; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[AR8X16_MAX_VLANS]; + u8 vlan_table[AR8X16_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR8X16_MAX_PORTS]; + int arl_age_time; + + /* mirroring */ + bool mirror_rx; + bool mirror_tx; + int source_port; + int monitor_port; + u8 port_vlan_prio[AR8X16_MAX_PORTS]; +}; + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum); +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val); +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg); +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val); +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data); +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data); +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg); +void +ar8xxx_phy_init(struct ar8xxx_priv *priv); +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan); +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan); +int +ar8xxx_sw_hw_apply(struct switch_dev *dev); +int +ar8xxx_sw_reset_switch(struct switch_dev *dev); +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link); +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +static inline struct ar8xxx_priv * +swdev_to_ar8xxx(struct switch_dev *swdev) +{ + return container_of(swdev, struct ar8xxx_priv, dev); +} + +static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_GIGE; +} + +static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; +} + +static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8216; +} + +static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8236; +} + +static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8316; +} + +static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8327; +} + +static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8337; +} + +static inline void +ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, 0, val); +} + +static inline void +ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, val, 0); +} + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +static inline void +wait_for_page_switch(void) +{ + udelay(5); +} + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/ar8327.c b/target/linux/generic/files-4.14/drivers/net/phy/ar8327.c new file mode 100644 index 0000000000..74f0a08d76 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/ar8327.c @@ -0,0 +1,1503 @@ +/* + * ar8327.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" +#include "ar8327.h" + +extern const struct ar8xxx_mib_desc ar8236_mibs[39]; +extern const struct switch_attr ar8xxx_sw_attr_vlan[1]; + +static u32 +ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) +{ + u32 t; + + if (!cfg) + return 0; + + t = 0; + switch (cfg->mode) { + case AR8327_PAD_NC: + break; + + case AR8327_PAD_MAC2MAC_MII: + t = AR8327_PAD_MAC_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2MAC_GMII: + t = AR8327_PAD_MAC_GMII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_SGMII: + t = AR8327_PAD_SGMII_EN; + + /* + * WAR for the QUalcomm Atheros AP136 board. + * It seems that RGMII TX/RX delay settings needs to be + * applied for SGMII mode as well, The ethernet is not + * reliable without this. + */ + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + + if (cfg->sgmii_delay_en) + t |= AR8327_PAD_SGMII_DELAY_EN; + + break; + + case AR8327_PAD_MAC2PHY_MII: + t = AR8327_PAD_PHY_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2PHY_GMII: + t = AR8327_PAD_PHY_GMII_EN; + if (cfg->pipe_rxclk_sel) + t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_RGMII: + t = AR8327_PAD_RGMII_EN; + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + break; + + case AR8327_PAD_PHY_GMII: + t = AR8327_PAD_PHYX_GMII_EN; + break; + + case AR8327_PAD_PHY_RGMII: + t = AR8327_PAD_PHYX_RGMII_EN; + break; + + case AR8327_PAD_PHY_MII: + t = AR8327_PAD_PHYX_MII_EN; + break; + } + + return t; +} + +static void +ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) +{ + switch (priv->chip_rev) { + case 1: + /* For 100M waveform */ + ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); + /* Turn on Gigabit clock */ + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); + break; + + case 2: + ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); + /* fallthrough */ + case 4: + ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); + ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); + ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); + break; + } +} + +static u32 +ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) +{ + u32 t; + + if (!cfg->force_link) + return AR8216_PORT_STATUS_LINK_AUTO; + + t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; + t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; + t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; + t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; + + switch (cfg->speed) { + case AR8327_PORT_SPEED_10: + t |= AR8216_PORT_SPEED_10M; + break; + case AR8327_PORT_SPEED_100: + t |= AR8216_PORT_SPEED_100M; + break; + case AR8327_PORT_SPEED_1000: + t |= AR8216_PORT_SPEED_1000M; + break; + } + + return t; +} + +#define AR8327_LED_ENTRY(_num, _reg, _shift) \ + [_num] = { .reg = (_reg), .shift = (_shift) } + +static const struct ar8327_led_entry +ar8327_led_map[AR8327_NUM_LEDS] = { + AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), + + AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), + AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), + AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), + + AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), + AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), + + AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), + AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), + AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), + + AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), +}; + +static void +ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, + enum ar8327_led_pattern pattern) +{ + const struct ar8327_led_entry *entry; + + entry = &ar8327_led_map[led_num]; + ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), + (3 << entry->shift), pattern << entry->shift); +} + +static void +ar8327_led_work_func(struct work_struct *work) +{ + struct ar8327_led *aled; + u8 pattern; + + aled = container_of(work, struct ar8327_led, led_work); + + pattern = aled->pattern; + + ar8327_set_led_pattern(aled->sw_priv, aled->led_num, + pattern); +} + +static void +ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) +{ + if (aled->pattern == pattern) + return; + + aled->pattern = pattern; + schedule_work(&aled->led_work); +} + +static inline struct ar8327_led * +led_cdev_to_ar8327_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct ar8327_led, cdev); +} + +static int +ar8327_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + + if (*delay_on == 0 && *delay_off == 0) { + *delay_on = 125; + *delay_off = 125; + } + + if (*delay_on != 125 || *delay_off != 125) { + /* + * The hardware only supports blinking at 4Hz. Fall back + * to software implementation in other cases. + */ + return -EINVAL; + } + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); + + spin_unlock(&aled->lock); + + return 0; +} + +static void +ar8327_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + bool active; + + active = (brightness != LED_OFF); + active ^= aled->active_low; + + pattern = (active) ? AR8327_LED_PATTERN_ON : + AR8327_LED_PATTERN_OFF; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); +} + +static ssize_t +ar8327_led_enable_hw_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + ssize_t ret = 0; + + ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); + + return ret; +} + +static ssize_t +ar8327_led_enable_hw_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + u8 value; + int ret; + + ret = kstrtou8(buf, 10, &value); + if (ret < 0) + return -EINVAL; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = !!value; + if (aled->enable_hw_mode) + pattern = AR8327_LED_PATTERN_RULE; + else + pattern = AR8327_LED_PATTERN_OFF; + + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); + + return size; +} + +static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, + ar8327_led_enable_hw_mode_show, + ar8327_led_enable_hw_mode_store); + +static int +ar8327_led_register(struct ar8327_led *aled) +{ + int ret; + + ret = led_classdev_register(NULL, &aled->cdev); + if (ret < 0) + return ret; + + if (aled->mode == AR8327_LED_MODE_HW) { + ret = device_create_file(aled->cdev.dev, + &dev_attr_enable_hw_mode); + if (ret) + goto err_unregister; + } + + return 0; + +err_unregister: + led_classdev_unregister(&aled->cdev); + return ret; +} + +static void +ar8327_led_unregister(struct ar8327_led *aled) +{ + if (aled->mode == AR8327_LED_MODE_HW) + device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); + + led_classdev_unregister(&aled->cdev); + cancel_work_sync(&aled->led_work); +} + +static int +ar8327_led_create(struct ar8xxx_priv *priv, + const struct ar8327_led_info *led_info) +{ + struct ar8327_data *data = priv->chip_data; + struct ar8327_led *aled; + int ret; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return 0; + + if (!led_info->name) + return -EINVAL; + + if (led_info->led_num >= AR8327_NUM_LEDS) + return -EINVAL; + + aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, + GFP_KERNEL); + if (!aled) + return -ENOMEM; + + aled->sw_priv = priv; + aled->led_num = led_info->led_num; + aled->active_low = led_info->active_low; + aled->mode = led_info->mode; + + if (aled->mode == AR8327_LED_MODE_HW) + aled->enable_hw_mode = true; + + aled->name = (char *)(aled + 1); + strcpy(aled->name, led_info->name); + + aled->cdev.name = aled->name; + aled->cdev.brightness_set = ar8327_led_set_brightness; + aled->cdev.blink_set = ar8327_led_blink_set; + aled->cdev.default_trigger = led_info->default_trigger; + + spin_lock_init(&aled->lock); + mutex_init(&aled->mutex); + INIT_WORK(&aled->led_work, ar8327_led_work_func); + + ret = ar8327_led_register(aled); + if (ret) + goto err_free; + + data->leds[data->num_leds++] = aled; + + return 0; + +err_free: + kfree(aled); + return ret; +} + +static void +ar8327_led_destroy(struct ar8327_led *aled) +{ + ar8327_led_unregister(aled); + kfree(aled); +} + +static void +ar8327_leds_init(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + + if (aled->enable_hw_mode) + aled->pattern = AR8327_LED_PATTERN_RULE; + else + aled->pattern = AR8327_LED_PATTERN_OFF; + + ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); + } +} + +static void +ar8327_leds_cleanup(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + ar8327_led_destroy(aled); + } + + kfree(data->leds); +} + +static int +ar8327_hw_config_pdata(struct ar8xxx_priv *priv, + struct ar8327_platform_data *pdata) +{ + struct ar8327_led_cfg *led_cfg; + struct ar8327_data *data = priv->chip_data; + u32 pos, new_pos; + u32 t; + + if (!pdata) + return -EINVAL; + + priv->get_port_link = pdata->get_port_link; + + data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); + data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); + + t = ar8327_get_pad_cfg(pdata->pad0_cfg); + if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) + t |= AR8337_PAD_MAC06_EXCHANGE_EN; + ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); + + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad6_cfg); + ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); + + pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRIP); + new_pos = pos; + + led_cfg = pdata->led_cfg; + if (led_cfg) { + if (led_cfg->open_drain) + new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; + else + new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; + + ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); + ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); + ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); + ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); + + if (new_pos != pos) + new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; + } + + if (pdata->sgmii_cfg) { + t = pdata->sgmii_cfg->sgmii_ctrl; + if (priv->chip_rev == 1) + t |= AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX; + else + t &= ~(AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX); + + ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t); + + if (pdata->sgmii_cfg->serdes_aen) + new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; + else + new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; + } + + ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); + + if (pdata->leds && pdata->num_leds) { + int i; + + data->leds = kzalloc(pdata->num_leds * sizeof(void *), + GFP_KERNEL); + if (!data->leds) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) + ar8327_led_create(priv, &pdata->leds[i]); + } + + return 0; +} + +#ifdef CONFIG_OF +static int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + struct ar8327_data *data = priv->chip_data; + const __be32 *paddr; + int len; + int i; + + paddr = of_get_property(np, "qca,ar8327-initvals", &len); + if (!paddr || len < (2 * sizeof(*paddr))) + return -EINVAL; + + len /= sizeof(*paddr); + + for (i = 0; i < len - 1; i += 2) { + u32 reg; + u32 val; + + reg = be32_to_cpup(paddr + i); + val = be32_to_cpup(paddr + i + 1); + + switch (reg) { + case AR8327_REG_PORT_STATUS(0): + data->port0_status = val; + break; + case AR8327_REG_PORT_STATUS(6): + data->port6_status = val; + break; + default: + ar8xxx_write(priv, reg, val); + break; + } + } + + return 0; +} +#else +static inline int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + return -EINVAL; +} +#endif + +static int +ar8327_hw_init(struct ar8xxx_priv *priv) +{ + int ret; + + priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL); + if (!priv->chip_data) + return -ENOMEM; + + if (priv->phy->mdio.dev.of_node) + ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node); + else + ret = ar8327_hw_config_pdata(priv, + priv->phy->mdio.dev.platform_data); + + if (ret) + return ret; + + ar8327_leds_init(priv); + + ar8xxx_phy_init(priv); + + return 0; +} + +static void +ar8327_cleanup(struct ar8xxx_priv *priv) +{ + ar8327_leds_cleanup(priv); +} + +static void +ar8327_init_globals(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + int i; + + /* enable CPU port and disable mirror port */ + t = AR8327_FWD_CTRL0_CPU_PORT_EN | + AR8327_FWD_CTRL0_MIRROR_PORT; + ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t); + + /* forward multicast and broadcast frames to CPU */ + t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); + ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, + AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, + AR8327_MODULE_EN_MIB); + + /* Disable EEE on all phy's due to stability issues */ + for (i = 0; i < AR8XXX_NUM_PHYS; i++) + data->eee[i] = false; +} + +static void +ar8327_init_port(struct ar8xxx_priv *priv, int port) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + + if (port == AR8216_PORT_CPU) + t = data->port0_status; + else if (port == 6) + t = data->port6_status; + else + t = AR8216_PORT_STATUS_LINK_AUTO; + + if (port != AR8216_PORT_CPU && port != 6) { + /*hw limitation:if configure mac when there is traffic, + port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); + msleep(100); + t |= AR8216_PORT_STATUS_FLOW_CONTROL; + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } else { + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } + + ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); + + t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = AR8327_PORT_LOOKUP_LEARN; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static u32 +ar8327_read_port_status(struct ar8xxx_priv *priv, int port) +{ + u32 t; + + t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); + /* map the flow control autoneg result bits to the flow control bits + * used in forced mode to allow ar8216_read_port_link detect + * flow control properly if autoneg is used + */ + if (t & AR8216_PORT_STATUS_LINK_UP && + t & AR8216_PORT_STATUS_LINK_AUTO) { + t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); + if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) + t |= AR8216_PORT_STATUS_TXFLOW; + if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) + t |= AR8216_PORT_STATUS_RXFLOW; + } + + return t; +} + +static u32 +ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) +{ + int phy; + u16 t; + + if (port >= priv->dev.ports) + return 0; + + if (port == 0 || port == 6) + return 0; + + phy = port - 1; + + /* EEE Ability Auto-negotiation Result */ + t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); + + return mmd_eee_adv_to_ethtool_adv_t(t); +} + +static int +ar8327_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_OP_FLUSH | + AR8327_ATU_FUNC_BUSY); + + return ret; +} + +static int +ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) { + t = (port << AR8327_ATU_PORT_NUM_S); + t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; + t |= AR8327_ATU_FUNC_BUSY; + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); + } + + return ret; +} + +static int +ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) +{ + u32 fwd_ctrl, frame_ack; + + fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port)); + + return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & + fwd_ctrl) == fwd_ctrl && + (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & + frame_ack) == frame_ack; +} + +static void +ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) +{ + int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); + u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port); + + if (enable) { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); + } else { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); + ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); + } +} + +static void +ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, + AR8327_VTU_FUNC1_BUSY, 0)) + return; + + if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) + ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val); + + op |= AR8327_VTU_FUNC1_BUSY; + ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op); +} + +static void +ar8327_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); +} + +static void +ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + u32 val; + int i; + + op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); + val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; + for (i = 0; i < AR8327_NUM_PORTS; i++) { + u32 mode; + + if ((port_mask & BIT(i)) == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_NOT; + else if (priv->vlan == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; + else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid)) + mode = AR8327_VTU_FUNC0_EG_MODE_TAG; + else + mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; + + val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); + } + ar8327_vtu_op(priv, op, val); +} + +static void +ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 t; + u32 egress, ingress; + u32 pvid = priv->vlan_id[priv->pvid[port]]; + + if (priv->vlan) { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; + ingress = AR8216_IN_SECURE; + } else { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; + ingress = AR8216_IN_PORT_ONLY; + } + + t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; + t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; + if (priv->vlan && priv->port_vlan_prio[port]) { + u32 prio = priv->port_vlan_prio[port]; + + t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S; + t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S; + } + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); + + t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; + t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S; + if (priv->vlan && priv->port_vlan_prio[port]) + t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP; + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = members; + t |= AR8327_PORT_LOOKUP_LEARN; + t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static int +ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (val->port_vlan == priv->pvid[p->id]) { + priv->vlan_tagged |= (1 << p->id); + } + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8327_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + for (port = 0; port < AR8327_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8327_NUM_PORTS || + priv->monitor_port >= AR8327_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); +} + +static int +ar8327_sw_set_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + data->eee[phy] = !!(val->value.i); + + return 0; +} + +static int +ar8327_sw_get_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + val->value.i = data->eee[phy]; + + return 0; +} + +static void +ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8327: timeout waiting for atu to become ready\n"); +} + +static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_data0, r1_data1, r1_data2, r1_func; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); + r2 |= 0x10; + + r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; + r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; + r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8327_wait_atu_ready(priv, r2, r1_func); + + ar8xxx_mii_write32(priv, r2, r1_data0, 0); + ar8xxx_mii_write32(priv, r2, r1_data1, 0); + ar8xxx_mii_write32(priv, r2, r1_data2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + ar8xxx_mii_write32(priv, r2, r1_func, + AR8327_ATU_FUNC_OP_GET_NEXT | + AR8327_ATU_FUNC_BUSY); + ar8327_wait_atu_ready(priv, r2, r1_func); + + val0 = ar8xxx_mii_read32(priv, r2, r1_data0); + val1 = ar8xxx_mii_read32(priv, r2, r1_data1); + val2 = ar8xxx_mii_read32(priv, r2, r1_data2); + + *status = val2 & AR8327_ATU_STATUS; + if (!*status) + break; + + i = 0; + t = AR8327_ATU_PORT0; + while (!(val1 & t) && ++i < AR8327_NUM_PORTS) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; + a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; + a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; + a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; + a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; + a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; + break; + } +} + +static int +ar8327_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int ret, i; + + ret = ar8xxx_sw_hw_apply(dev); + if (ret) + return ret; + + for (i=0; i < AR8XXX_NUM_PHYS; i++) { + if (data->eee[i]) + ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + else + ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + } + + return 0; +} + +int +ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + val->value.i = ar8327_get_port_igmp(priv, port); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ar8327_set_port_igmp(priv, port, val->value.i); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_get_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || + !val->value.i) + break; + } + + return 0; +} + +int +ar8327_sw_set_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) + break; + } + + return 0; +} + +int +ar8327_sw_get_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u32 val_reg; + + mutex_lock(&priv->reg_mutex); + val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); + val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + if (val->value.i) + ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + else + ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static int +ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + if (val->value.i < 0 || val->value.i > 7) + return -EINVAL; + + priv->port_vlan_prio[port] = val->value.i; + + return 0; +} + +static int +ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + val->value.i = priv->port_vlan_prio[port]; + + return 0; +} + +static const struct switch_attr ar8327_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "arl_age_time", + .description = "ARL age time (secs)", + .set = ar8xxx_sw_set_arl_age_time, + .get = ar8xxx_sw_get_arl_age_time, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable IGMP Snooping", + .set = ar8327_sw_set_igmp_snooping, + .get = ar8327_sw_get_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_v3", + .description = "Enable IGMPv3 support", + .set = ar8327_sw_set_igmp_v3, + .get = ar8327_sw_get_igmp_v3, + .max = 1 + }, +}; + +static const struct switch_attr ar8327_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_eee", + .description = "Enable EEE PHY sleep mode", + .set = ar8327_sw_set_eee, + .get = ar8327_sw_get_eee, + .max = 1, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable port's IGMP Snooping", + .set = ar8327_sw_set_port_igmp_snooping, + .get = ar8327_sw_get_port_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "vlan_prio", + .description = "Port VLAN default priority (VLAN PCP) (0-7)", + .set = ar8327_sw_set_port_vlan_prio, + .get = ar8327_sw_get_port_vlan_prio, + .max = 7, + }, +}; + +static const struct switch_dev_ops ar8327_sw_ops = { + .attr_global = { + .attr = ar8327_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), + }, + .attr_port = { + .attr = ar8327_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8327_sw_get_ports, + .set_vlan_ports = ar8327_sw_set_ports, + .apply_config = ar8327_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +const struct ar8xxx_chip ar8327_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8327", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; + +const struct ar8xxx_chip ar8337_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8337", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; diff --git a/target/linux/generic/files-4.14/drivers/net/phy/ar8327.h b/target/linux/generic/files-4.14/drivers/net/phy/ar8327.h new file mode 100644 index 0000000000..d53ef885b1 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/ar8327.h @@ -0,0 +1,325 @@ +/* + * ar8327.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8327_H +#define __AR8327_H + +#define AR8327_NUM_PORTS 7 +#define AR8327_NUM_LEDS 15 +#define AR8327_PORTS_ALL 0x7f +#define AR8327_NUM_LED_CTRL_REGS 4 + +#define AR8327_REG_MASK 0x000 + +#define AR8327_REG_PAD0_MODE 0x004 +#define AR8327_REG_PAD5_MODE 0x008 +#define AR8327_REG_PAD6_MODE 0x00c +#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define AR8327_PAD_MAC_MII_EN BIT(2) +#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define AR8327_PAD_MAC_GMII_EN BIT(6) +#define AR8327_PAD_SGMII_EN BIT(7) +#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define AR8327_PAD_PHY_MII_EN BIT(10) +#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define AR8327_PAD_PHY_GMII_EN BIT(14) +#define AR8327_PAD_PHYX_GMII_EN BIT(16) +#define AR8327_PAD_PHYX_RGMII_EN BIT(17) +#define AR8327_PAD_PHYX_MII_EN BIT(18) +#define AR8327_PAD_SGMII_DELAY_EN BIT(19) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define AR8327_PAD_RGMII_EN BIT(26) + +#define AR8327_REG_POWER_ON_STRIP 0x010 +#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) +#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) +#define AR8327_POWER_ON_STRIP_SERDES_AEN BIT(7) + +#define AR8327_REG_INT_STATUS0 0x020 +#define AR8327_INT0_VT_DONE BIT(20) + +#define AR8327_REG_INT_STATUS1 0x024 +#define AR8327_REG_INT_MASK0 0x028 +#define AR8327_REG_INT_MASK1 0x02c + +#define AR8327_REG_MODULE_EN 0x030 +#define AR8327_MODULE_EN_MIB BIT(0) + +#define AR8327_REG_MIB_FUNC 0x034 +#define AR8327_MIB_CPU_KEEP BIT(20) + +#define AR8327_REG_SERVICE_TAG 0x048 +#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4) +#define AR8327_REG_LED_CTRL0 0x050 +#define AR8327_REG_LED_CTRL1 0x054 +#define AR8327_REG_LED_CTRL2 0x058 +#define AR8327_REG_LED_CTRL3 0x05c +#define AR8327_REG_MAC_ADDR0 0x060 +#define AR8327_REG_MAC_ADDR1 0x064 + +#define AR8327_REG_MAX_FRAME_SIZE 0x078 +#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) + +#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) +#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10) +#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11) + +#define AR8327_REG_HEADER_CTRL 0x098 +#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) + +#define AR8327_REG_SGMII_CTRL 0x0e0 +#define AR8327_SGMII_CTRL_EN_PLL BIT(1) +#define AR8327_SGMII_CTRL_EN_RX BIT(2) +#define AR8327_SGMII_CTRL_EN_TX BIT(3) + +#define AR8327_REG_EEE_CTRL 0x100 +#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2) + +#define AR8327_REG_FRAME_ACK_CTRL0 0x210 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30) + +#define AR8327_REG_FRAME_ACK_CTRL1 0x214 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24) +#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25) + +#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6) +#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8) + +#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3) +#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define AR8327_PORT_VLAN0_DEF_SVID_S 0 +#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3) +#define AR8327_PORT_VLAN0_DEF_SPRI_S 13 +#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define AR8327_PORT_VLAN0_DEF_CVID_S 16 +#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3) +#define AR8327_PORT_VLAN0_DEF_CPRI_S 29 + +#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4) +#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define AR8327_PORT_VLAN1_OUT_MODE_S 12 +#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +#define AR8327_REG_ATU_DATA0 0x600 +#define AR8327_ATU_ADDR0 BITS(0, 8) +#define AR8327_ATU_ADDR0_S 0 +#define AR8327_ATU_ADDR1 BITS(8, 8) +#define AR8327_ATU_ADDR1_S 8 +#define AR8327_ATU_ADDR2 BITS(16, 8) +#define AR8327_ATU_ADDR2_S 16 +#define AR8327_ATU_ADDR3 BITS(24, 8) +#define AR8327_ATU_ADDR3_S 24 +#define AR8327_REG_ATU_DATA1 0x604 +#define AR8327_ATU_ADDR4 BITS(0, 8) +#define AR8327_ATU_ADDR4_S 0 +#define AR8327_ATU_ADDR5 BITS(8, 8) +#define AR8327_ATU_ADDR5_S 8 +#define AR8327_ATU_PORTS BITS(16, 7) +#define AR8327_ATU_PORT0 BIT(16) +#define AR8327_ATU_PORT1 BIT(17) +#define AR8327_ATU_PORT2 BIT(18) +#define AR8327_ATU_PORT3 BIT(19) +#define AR8327_ATU_PORT4 BIT(20) +#define AR8327_ATU_PORT5 BIT(21) +#define AR8327_ATU_PORT6 BIT(22) +#define AR8327_REG_ATU_DATA2 0x608 +#define AR8327_ATU_STATUS BITS(0, 4) + +#define AR8327_REG_ATU_FUNC 0x60c +#define AR8327_ATU_FUNC_OP BITS(0, 4) +#define AR8327_ATU_FUNC_OP_NOOP 0x0 +#define AR8327_ATU_FUNC_OP_FLUSH 0x1 +#define AR8327_ATU_FUNC_OP_LOAD 0x2 +#define AR8327_ATU_FUNC_OP_PURGE 0x3 +#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4 +#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5 +#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 +#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 +#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 +#define AR8327_ATU_PORT_NUM BITS(8, 4) +#define AR8327_ATU_PORT_NUM_S 8 +#define AR8327_ATU_FUNC_BUSY BIT(31) + +#define AR8327_REG_VTU_FUNC0 0x0610 +#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) +#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 +#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 +#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 +#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 +#define AR8327_VTU_FUNC0_IVL BIT(19) +#define AR8327_VTU_FUNC0_VALID BIT(20) + +#define AR8327_REG_VTU_FUNC1 0x0614 +#define AR8327_VTU_FUNC1_OP BITS(0, 3) +#define AR8327_VTU_FUNC1_OP_NOOP 0 +#define AR8327_VTU_FUNC1_OP_FLUSH 1 +#define AR8327_VTU_FUNC1_OP_LOAD 2 +#define AR8327_VTU_FUNC1_OP_PURGE 3 +#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 +#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 +#define AR8327_VTU_FUNC1_OP_GET_ONE 6 +#define AR8327_VTU_FUNC1_FULL BIT(4) +#define AR8327_VTU_FUNC1_PORT BIT(8, 4) +#define AR8327_VTU_FUNC1_PORT_S 8 +#define AR8327_VTU_FUNC1_VID BIT(16, 12) +#define AR8327_VTU_FUNC1_VID_S 16 +#define AR8327_VTU_FUNC1_BUSY BIT(31) + +#define AR8327_REG_ARL_CTRL 0x0618 + +#define AR8327_REG_FWD_CTRL0 0x620 +#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) +#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) +#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 + +#define AR8327_REG_FWD_CTRL1 0x624 +#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) +#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 +#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) +#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 +#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) +#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 +#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) +#define AR8327_FWD_CTRL1_IGMP_S 24 + +#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) +#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define AR8327_PORT_LOOKUP_IN_MODE_S 8 +#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) +#define AR8327_PORT_LOOKUP_STATE_S 16 +#define AR8327_PORT_LOOKUP_LEARN BIT(20) +#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25) + +#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) + +#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) +#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) + +#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31) + +enum ar8327_led_pattern { + AR8327_LED_PATTERN_OFF = 0, + AR8327_LED_PATTERN_BLINK, + AR8327_LED_PATTERN_ON, + AR8327_LED_PATTERN_RULE, +}; + +struct ar8327_led_entry { + unsigned reg; + unsigned shift; +}; + +struct ar8327_led { + struct led_classdev cdev; + struct ar8xxx_priv *sw_priv; + + char *name; + bool active_low; + u8 led_num; + enum ar8327_led_mode mode; + + struct mutex mutex; + spinlock_t lock; + struct work_struct led_work; + bool enable_hw_mode; + enum ar8327_led_pattern pattern; +}; + +struct ar8327_data { + u32 port0_status; + u32 port6_status; + + struct ar8327_led **leds; + unsigned int num_leds; + + /* all fields below are cleared on reset */ + bool eee[AR8XXX_NUM_PHYS]; +}; + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/Kconfig b/target/linux/generic/files-4.14/drivers/net/phy/b53/Kconfig new file mode 100644 index 0000000000..08287e7adf --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/Kconfig @@ -0,0 +1,37 @@ +menuconfig SWCONFIG_B53 + tristate "Broadcom bcm53xx managed switch support" + depends on SWCONFIG + help + This driver adds support for Broadcom managed switch chips. It supports + BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX + integrated switches. + +config SWCONFIG_B53_SPI_DRIVER + tristate "B53 SPI connected switch driver" + depends on SWCONFIG_B53 && SPI + help + Select to enable support for registering switches configured through SPI. + +config SWCONFIG_B53_PHY_DRIVER + tristate "B53 MDIO connected switch driver" + depends on SWCONFIG_B53 + select SWCONFIG_B53_PHY_FIXUP + help + Select to enable support for registering switches configured through MDIO. + +config SWCONFIG_B53_MMAP_DRIVER + tristate "B53 MMAP connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped switches like the BCM63XX + integrated switches. + +config SWCONFIG_B53_SRAB_DRIVER + tristate "B53 SRAB connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped Switch Register Access + Bridge Registers (SRAB) like it is found on the BCM53010 + +config SWCONFIG_B53_PHY_FIXUP + bool diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/Makefile b/target/linux/generic/files-4.14/drivers/net/phy/b53/Makefile new file mode 100644 index 0000000000..13ff366448 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_SWCONFIG_B53) += b53_common.o + +obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o + +obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o +obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o +obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o +obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o + +ccflags-y += -Werror diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_common.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_common.c new file mode 100644 index 0000000000..670588c84e --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_common.c @@ -0,0 +1,1722 @@ +/* + * B53 switch driver main logic + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b53_regs.h" +#include "b53_priv.h" + +/* buffer size needed for displaying all MIBs with max'd values */ +#define B53_BUF_SIZE 1188 + +struct b53_mib_desc { + u8 size; + u8 offset; + const char *name; +}; + +/* BCM5365 MIB counters */ +static const struct b53_mib_desc b53_mibs_65[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { }, +}; + +#define B63XX_MIB_TXB_ID 0 /* TxOctets */ +#define B63XX_MIB_RXB_ID 14 /* RxOctets */ + +/* BCM63xx MIB counters */ +static const struct b53_mib_desc b53_mibs_63xx[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x0c, "TxQoSPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x3c, "TxQoSOctets" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { 4, 0xa0, "RxSymbolErrors" }, + { 4, 0xa4, "RxQoSPkts" }, + { 8, 0xa8, "RxQoSOctets" }, + { 4, 0xb0, "Pkts1523to2047Octets" }, + { 4, 0xb4, "Pkts2048to4095Octets" }, + { 4, 0xb8, "Pkts4096to8191Octets" }, + { 4, 0xbc, "Pkts8192to9728Octets" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +#define B53XX_MIB_TXB_ID 0 /* TxOctets */ +#define B53XX_MIB_RXB_ID 12 /* RxOctets */ + +/* MIB counters */ +static const struct b53_mib_desc b53_mibs[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x50, "RxOctets" }, + { 4, 0x58, "RxUndersizePkts" }, + { 4, 0x5c, "RxPausePkts" }, + { 4, 0x60, "Pkts64Octets" }, + { 4, 0x64, "Pkts65to127Octets" }, + { 4, 0x68, "Pkts128to255Octets" }, + { 4, 0x6c, "Pkts256to511Octets" }, + { 4, 0x70, "Pkts512to1023Octets" }, + { 4, 0x74, "Pkts1024to1522Octets" }, + { 4, 0x78, "RxOversizePkts" }, + { 4, 0x7c, "RxJabbers" }, + { 4, 0x80, "RxAlignmentErrors" }, + { 4, 0x84, "RxFCSErrors" }, + { 8, 0x88, "RxGoodOctets" }, + { 4, 0x90, "RxDropPkts" }, + { 4, 0x94, "RxUnicastPkts" }, + { 4, 0x98, "RxMulticastPkts" }, + { 4, 0x9c, "RxBroadcastPkts" }, + { 4, 0xa0, "RxSAChanges" }, + { 4, 0xa4, "RxFragments" }, + { 4, 0xa8, "RxJumboPkts" }, + { 4, 0xac, "RxSymbolErrors" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +static int b53_do_vlan_op(struct b53_device *dev, u8 op) +{ + unsigned int i; + + b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); + + for (i = 0; i < 10; i++) { + u8 vta; + + b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); + if (!(vta & VTA_START_CMD)) + return 0; + + usleep_range(100, 200); + } + + return -EIO; +} + +static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, + u16 untag) +{ + if (is5325(dev)) { + u32 entry = 0; + + if (members) { + entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | + members; + if (dev->core_rev >= 3) + entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; + else + entry |= VA_VALID_25; + } + + b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else if (is5365(dev)) { + u16 entry = 0; + + if (members) + entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | + members | VA_VALID_65; + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else { + b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); + b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], + (untag << VTE_UNTAG_S) | members); + + b53_do_vlan_op(dev, VTA_CMD_WRITE); + } +} + +void b53_set_forwarding(struct b53_device *dev, int enable) +{ + u8 mgmt; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (enable) + mgmt |= SM_SW_FWD_EN; + else + mgmt &= ~SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static void b53_enable_vlan(struct b53_device *dev, int enable) +{ + u8 mgmt, vc0, vc1, vc4 = 0, vc5; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); + + if (is5325(dev) || is5365(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); + } else if (is63xx(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); + } else { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); + } + + mgmt &= ~SM_SW_FWD_MODE; + + if (enable) { + vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; + vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; + vc5 |= VC5_DROP_VTABLE_MISS; + + if (is5325(dev)) + vc0 &= ~VC0_RESERVED_1; + + if (is5325(dev) || is5365(dev)) + vc1 |= VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) { + if (dev->allow_vid_4095) + vc5 |= VC5_VID_FFF_EN; + else + vc5 &= ~VC5_VID_FFF_EN; + } + } else { + vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); + vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc5 &= ~VC5_DROP_VTABLE_MISS; + + if (is5325(dev) || is5365(dev)) + vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; + else + vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; + + if (is5325(dev) || is5365(dev)) + vc1 &= ~VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) + vc5 &= ~VC5_VID_FFF_EN; + } + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); + + if (is5325(dev) || is5365(dev)) { + /* enable the high 8 bit vid check on 5325 */ + if (is5325(dev) && enable) + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, + VC3_HIGH_8BIT_EN); + else + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); + } else if (is63xx(dev)) { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); + } else { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); + } + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) +{ + u32 port_mask = 0; + u16 max_size = JMS_MIN_SIZE; + + if (is5325(dev) || is5365(dev)) + return -EINVAL; + + if (enable) { + port_mask = dev->enabled_ports; + max_size = JMS_MAX_SIZE; + if (allow_10_100) + port_mask |= JPM_10_100_JUMBO_EN; + } + + b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); + return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); +} + +static int b53_flush_arl(struct b53_device *dev) +{ + unsigned int i; + + b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); + + for (i = 0; i < 10; i++) { + u8 fast_age_ctrl; + + b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + &fast_age_ctrl); + + if (!(fast_age_ctrl & FAST_AGE_DONE)) + return 0; + + mdelay(1); + } + + pr_warn("time out while flushing ARL\n"); + + return -EINVAL; +} + +static void b53_enable_ports(struct b53_device *dev) +{ + unsigned i; + + b53_for_each_port(dev, i) { + u8 port_ctrl; + u16 pvlan_mask; + + /* + * prevent leaking packets between wan and lan in unmanaged + * mode through port vlans. + */ + if (dev->enable_vlan || is_cpu_port(dev, i)) + pvlan_mask = 0x1ff; + else if (is531x5(dev) || is5301x(dev)) + /* BCM53115 may use a different port as cpu port */ + pvlan_mask = BIT(dev->sw_dev.cpu_port); + else + pvlan_mask = BIT(B53_CPU_PORT); + + /* BCM5325 CPU port is at 8 */ + if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) + i = B53_CPU_PORT; + + if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) + /* disable unused ports 6 & 7 */ + port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; + else if (i == B53_CPU_PORT) + port_ctrl = PORT_CTRL_RX_BCST_EN | + PORT_CTRL_RX_MCST_EN | + PORT_CTRL_RX_UCST_EN; + else + port_ctrl = 0; + + b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), + pvlan_mask); + + /* port state is handled by bcm63xx_enet driver */ + if (!is63xx(dev) && !(is5301x(dev) && i == 6)) + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), + port_ctrl); + } +} + +static void b53_enable_mib(struct b53_device *dev) +{ + u8 gc; + + b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); + + b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); +} + +static int b53_apply(struct b53_device *dev) +{ + int i; + + /* clear all vlan entries */ + if (is5325(dev) || is5365(dev)) { + for (i = 1; i < dev->sw_dev.vlans; i++) + b53_set_vlan_entry(dev, i, 0, 0); + } else { + b53_do_vlan_op(dev, VTA_CMD_CLEAR); + } + + b53_enable_vlan(dev, dev->enable_vlan); + + /* fill VLAN table */ + if (dev->enable_vlan) { + for (i = 0; i < dev->sw_dev.vlans; i++) { + struct b53_vlan *vlan = &dev->vlans[i]; + + if (!vlan->members) + continue; + + b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); + } + + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), + dev->ports[i].pvid); + } else { + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), 1); + + } + + b53_enable_ports(dev); + + if (!is5325(dev) && !is5365(dev)) + b53_set_jumbo(dev, dev->enable_jumbo, 1); + + return 0; +} + +static void b53_switch_reset_gpio(struct b53_device *dev) +{ + int gpio = dev->reset_gpio; + + if (gpio < 0) + return; + + /* + * Reset sequence: RESET low(50ms)->high(20ms) + */ + gpio_set_value(gpio, 0); + mdelay(50); + + gpio_set_value(gpio, 1); + mdelay(20); + + dev->current_page = 0xff; +} + +static int b53_configure_ports_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + u32 port_num; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + + for_each_available_child_of_node(dn, pn) { + struct device_node *fixed_link; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + fixed_link = of_get_child_by_name(pn, "fixed-link"); + if (fixed_link) { + u32 spd; + u8 po = GMII_PO_LINK; + int mode = of_get_phy_mode(pn); + + if (!of_property_read_u32(fixed_link, "speed", &spd)) { + switch (spd) { + case 10: + po |= GMII_PO_SPEED_10M; + break; + case 100: + po |= GMII_PO_SPEED_100M; + break; + case 2000: + if (is_imp_port(dev, port_num)) + po |= PORT_OVERRIDE_SPEED_2000M; + else + po |= GMII_PO_SPEED_2000M; + /* fall through */ + case 1000: + po |= GMII_PO_SPEED_1000M; + break; + } + } + + if (of_property_read_bool(fixed_link, "full-duplex")) + po |= PORT_OVERRIDE_FULL_DUPLEX; + if (of_property_read_bool(fixed_link, "pause")) + po |= GMII_PO_RX_FLOW; + if (of_property_read_bool(fixed_link, "asym-pause")) + po |= GMII_PO_TX_FLOW; + + if (is_imp_port(dev, port_num)) { + po |= PORT_OVERRIDE_EN; + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) + po |= PORT_OVERRIDE_RV_MII_25; + + b53_write8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, po); + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) { + b53_read8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, &po); + if (!(po & PORT_OVERRIDE_RV_MII_25)) + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } else { + po |= GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, + B53_GMII_PORT_OVERRIDE_CTRL(port_num), + po); + } + } + } + + return 0; +} + +static int b53_configure_ports(struct b53_device *dev) +{ + u8 cpu_port = dev->sw_dev.cpu_port; + + /* configure MII port if necessary */ + if (is5325(dev)) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + /* reverse mii needs to be enabled */ + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_RV_MII_25); + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } + } else if (is531x5(dev) && cpu_port == B53_CPU_PORT) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_EN | + PORT_OVERRIDE_LINK); + + /* BCM47189 has another interface connected to the port 5 */ + if (dev->enabled_ports & BIT(5)) { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } else if (is5301x(dev)) { + if (cpu_port == 8) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + mii_port_override |= PORT_OVERRIDE_LINK | + PORT_OVERRIDE_RX_FLOW | + PORT_OVERRIDE_TX_FLOW | + PORT_OVERRIDE_SPEED_2000M | + PORT_OVERRIDE_EN; + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override); + + /* TODO: Ports 5 & 7 require some extra handling */ + } else { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN | + GMII_PO_SPEED_2000M; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } + + return 0; +} + +static int b53_switch_reset(struct b53_device *dev) +{ + int ret = 0; + u8 mgmt; + + b53_switch_reset_gpio(dev); + + if (is539x(dev)) { + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); + } + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + mgmt &= ~SM_SW_FWD_MODE; + mgmt |= SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + pr_err("Failed to enable switch!\n"); + return -EINVAL; + } + } + + /* enable all ports */ + b53_enable_ports(dev); + + if (dev->dev->of_node) + ret = b53_configure_ports_of(dev); + else + ret = b53_configure_ports(dev); + + if (ret) + return ret; + + b53_enable_mib(dev); + + return b53_flush_arl(dev); +} + +/* + * Swconfig glue functions + */ + +static int b53_global_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_vlan; + + return 0; +} + +static int b53_global_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_vlan = val->value.i; + + return 0; +} + +static int b53_global_get_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_jumbo; + + return 0; +} + +static int b53_global_set_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_jumbo = val->value.i; + + return 0; +} + +static int b53_global_get_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->allow_vid_4095; + + return 0; +} + +static int b53_global_set_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->allow_vid_4095 = val->value.i; + + return 0; +} + +static int b53_global_get_ports(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", + priv->enabled_ports); + val->value.s = priv->buf; + + return 0; +} + +static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + *val = priv->ports[port].pvid; + + return 0; +} + +static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (val > 15 && is5325(priv)) + return -EINVAL; + if (val == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + priv->ports[port].pvid = val; + + return 0; +} + +static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port = &val->value.ports[0]; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + val->len = 0; + + if (!vlan->members) + return 0; + + for (i = 0; i < dev->ports; i++) { + if (!(vlan->members & BIT(i))) + continue; + + + if (!(vlan->untag & BIT(i))) + port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); + else + port->flags = 0; + + port->id = i; + val->len++; + port++; + } + + return 0; +} + +static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + /* only BCM5325 and BCM5365 supports VID 0 */ + if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) + return -EINVAL; + + /* VLAN 4095 needs special handling */ + if (val->port_vlan == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + port = &val->value.ports[0]; + vlan->members = 0; + vlan->untag = 0; + for (i = 0; i < val->len; i++, port++) { + vlan->members |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { + vlan->untag |= BIT(port->id); + priv->ports[port->id].pvid = val->port_vlan; + }; + } + + /* ignore disabled ports */ + vlan->members &= priv->enabled_ports; + vlan->untag &= priv->enabled_ports; + + return 0; +} + +static int b53_port_get_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (is_cpu_port(priv, port)) { + link->link = 1; + link->duplex = 1; + link->speed = is5325(priv) || is5365(priv) ? + SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; + link->aneg = 0; + } else if (priv->enabled_ports & BIT(port)) { + u32 speed; + u16 lnk, duplex; + + b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); + b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); + + lnk = (lnk >> port) & 1; + duplex = (duplex >> port) & 1; + + if (is5325(priv) || is5365(priv)) { + u16 tmp; + + b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); + speed = SPEED_PORT_FE(tmp, port); + } else { + b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); + speed = SPEED_PORT_GE(speed, port); + } + + link->link = lnk; + if (lnk) { + link->duplex = duplex; + switch (speed) { + case SPEED_STAT_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case SPEED_STAT_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case SPEED_STAT_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + } + + link->aneg = 1; + } else { + link->link = 0; + } + + return 0; + +} + +static int b53_port_set_link(struct switch_dev *sw_dev, int port, + struct switch_port_link *link) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + + /* + * TODO: BCM63XX requires special handling as it can have external phys + * and ports might be GE or only FE + */ + if (is63xx(dev)) + return -ENOTSUPP; + + if (port == sw_dev->cpu_port) + return -EINVAL; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && + (is5325(dev) || is5365(dev))) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex) + return -EINVAL; + + return switch_generic_set_link(sw_dev, port, link); +} + +static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_read16) + return priv->ops->phy_read16(priv, addr, reg, value); + + return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_write16) + return priv->ops->phy_write16(priv, addr, reg, value); + + return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_global_reset_switch(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* reset vlans */ + priv->enable_vlan = 0; + priv->enable_jumbo = 0; + priv->allow_vid_4095 = 0; + + memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans); + memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports); + + return b53_switch_reset(priv); +} + +static int b53_global_apply_config(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* disable switching */ + b53_set_forwarding(priv, 0); + + b53_apply(priv); + + /* enable switching */ + b53_set_forwarding(priv, 1); + + return 0; +} + + +static int b53_global_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + u8 gc; + + b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); + mdelay(1); + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); + mdelay(1); + + return 0; +} + +static int b53_port_get_mib(struct switch_dev *sw_dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int port = val->port_vlan; + int len = 0; + + if (!(BIT(port) & dev->enabled_ports)) + return -1; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + for (; mibs->size > 0; mibs++) { + u64 val; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, + &val32); + val = val32; + } + + len += snprintf(dev->buf + len, B53_BUF_SIZE - len, + "%-20s: %llu\n", mibs->name, val); + } + + val->len = len; + val->value.s = dev->buf; + + return 0; +} + +static int b53_port_get_stats(struct switch_dev *sw_dev, int port, + struct switch_port_stats *stats) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int txb_id, rxb_id; + u64 rxb, txb; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + txb_id = B53XX_MIB_TXB_ID; + rxb_id = B53XX_MIB_RXB_ID; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + txb_id = B63XX_MIB_TXB_ID; + rxb_id = B63XX_MIB_RXB_ID; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb); + b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32); + txb = val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32); + rxb = val32; + } + + stats->tx_bytes = txb; + stats->rx_bytes = rxb; + + return 0; +} + +static struct switch_attr b53_global_ops_25[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, +}; + +static struct switch_attr b53_global_ops_65[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, +}; + +static struct switch_attr b53_global_ops[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available Ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_jumbo", + .description = "Enable Jumbo Frames", + .set = b53_global_set_jumbo_enable, + .get = b53_global_get_jumbo_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "allow_vid_4095", + .description = "Allow VID 4095", + .set = b53_global_set_4095_enable, + .get = b53_global_get_4095_enable, + .max = 1, + }, +}; + +static struct switch_attr b53_port_ops[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .get = b53_port_get_mib, + }, +}; + +static struct switch_attr b53_no_ops[] = { +}; + +static const struct switch_dev_ops b53_switch_ops_25 = { + .attr_global = { + .attr = b53_global_ops_25, + .n_attr = ARRAY_SIZE(b53_global_ops_25), + }, + .attr_port = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops_65 = { + .attr_global = { + .attr = b53_global_ops_65, + .n_attr = ARRAY_SIZE(b53_global_ops_65), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops = { + .attr_global = { + .attr = b53_global_ops, + .n_attr = ARRAY_SIZE(b53_global_ops), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +struct b53_chip_data { + u32 chip_id; + const char *dev_name; + const char *alias; + u16 vlans; + u16 enabled_ports; + u8 cpu_port; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + const struct switch_dev_ops *sw_ops; +}; + +#define B53_VTA_REGS \ + { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } +#define B53_VTA_REGS_9798 \ + { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } +#define B53_VTA_REGS_63XX \ + { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } + +static const struct b53_chip_data b53_switch_chips[] = { + { + .chip_id = BCM5325_DEVICE_ID, + .dev_name = "BCM5325", + .alias = "bcm5325", + .vlans = 16, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_25, + }, + { + .chip_id = BCM5365_DEVICE_ID, + .dev_name = "BCM5365", + .alias = "bcm5365", + .vlans = 256, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_65, + }, + { + .chip_id = BCM5395_DEVICE_ID, + .dev_name = "BCM5395", + .alias = "bcm5395", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5397_DEVICE_ID, + .dev_name = "BCM5397", + .alias = "bcm5397", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5398_DEVICE_ID, + .dev_name = "BCM5398", + .alias = "bcm5398", + .vlans = 4096, + .enabled_ports = 0x7f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53115_DEVICE_ID, + .dev_name = "BCM53115", + .alias = "bcm53115", + .vlans = 4096, + .enabled_ports = 0x1f, + .vta_regs = B53_VTA_REGS, + .cpu_port = B53_CPU_PORT, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53125_DEVICE_ID, + .dev_name = "BCM53125", + .alias = "bcm53125", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53128_DEVICE_ID, + .dev_name = "BCM53128", + .alias = "bcm53128", + .vlans = 4096, + .enabled_ports = 0x1ff, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM63XX_DEVICE_ID, + .dev_name = "BCM63xx", + .alias = "bcm63xx", + .vlans = 4096, + .enabled_ports = 0, /* pdata must provide them */ + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_63XX, + .duplex_reg = B53_DUPLEX_STAT_63XX, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53010_DEVICE_ID, + .dev_name = "BCM53010", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53011_DEVICE_ID, + .dev_name = "BCM53011", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53012_DEVICE_ID, + .dev_name = "BCM53012", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53018_DEVICE_ID, + .dev_name = "BCM53018", + .alias = "bcm53018", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53019_DEVICE_ID, + .dev_name = "BCM53019", + .alias = "bcm53019", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, +}; + +static int b53_switch_init_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + const char *alias; + u32 port_num; + u16 ports = 0; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + if (!dn) + return -EINVAL; + + for_each_available_child_of_node(dn, pn) { + const char *label; + int len; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + ports |= BIT(port_num); + + label = of_get_property(pn, "label", &len); + if (label && !strcmp(label, "cpu")) + dev->sw_dev.cpu_port = port_num; + } + + dev->enabled_ports = ports; + + if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias", + &alias)) + dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL); + + return 0; +} + +static int b53_switch_init(struct b53_device *dev) +{ + struct switch_dev *sw_dev = &dev->sw_dev; + unsigned i; + int ret; + + for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { + const struct b53_chip_data *chip = &b53_switch_chips[i]; + + if (chip->chip_id == dev->chip_id) { + sw_dev->name = chip->dev_name; + if (!sw_dev->alias) + sw_dev->alias = chip->alias; + if (!dev->enabled_ports) + dev->enabled_ports = chip->enabled_ports; + dev->duplex_reg = chip->duplex_reg; + dev->vta_regs[0] = chip->vta_regs[0]; + dev->vta_regs[1] = chip->vta_regs[1]; + dev->vta_regs[2] = chip->vta_regs[2]; + dev->jumbo_pm_reg = chip->jumbo_pm_reg; + sw_dev->ops = chip->sw_ops; + sw_dev->cpu_port = chip->cpu_port; + sw_dev->vlans = chip->vlans; + break; + } + } + + if (!sw_dev->name) + return -EINVAL; + + /* check which BCM5325x version we have */ + if (is5325(dev)) { + u8 vc4; + + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + + /* check reserved bits */ + switch (vc4 & 3) { + case 1: + /* BCM5325E */ + break; + case 3: + /* BCM5325F - do not use port 4 */ + dev->enabled_ports &= ~BIT(4); + break; + default: +/* On the BCM47XX SoCs this is the supported internal switch.*/ +#ifndef CONFIG_BCM47XX + /* BCM5325M */ + return -EINVAL; +#else + break; +#endif + } + } else if (dev->chip_id == BCM53115_DEVICE_ID) { + u64 strap_value; + + b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); + /* use second IMP port if GMII is enabled */ + if (strap_value & SV_GMII_CTRL_115) + sw_dev->cpu_port = 5; + } + + if (dev_of_node(dev->dev)) { + ret = b53_switch_init_of(dev); + if (ret) + return ret; + } + + dev->enabled_ports |= BIT(sw_dev->cpu_port); + sw_dev->ports = fls(dev->enabled_ports); + + dev->ports = devm_kzalloc(dev->dev, + sizeof(struct b53_port) * sw_dev->ports, + GFP_KERNEL); + if (!dev->ports) + return -ENOMEM; + + dev->vlans = devm_kzalloc(dev->dev, + sizeof(struct b53_vlan) * sw_dev->vlans, + GFP_KERNEL); + if (!dev->vlans) + return -ENOMEM; + + dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); + if (!dev->buf) + return -ENOMEM; + + dev->reset_gpio = b53_switch_get_reset_gpio(dev); + if (dev->reset_gpio >= 0) { + ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, + GPIOF_OUT_INIT_HIGH, "robo_reset"); + if (ret) + return ret; + } + + return b53_switch_reset(dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv) +{ + struct b53_device *dev; + + dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->dev = base; + dev->ops = ops; + dev->priv = priv; + mutex_init(&dev->reg_mutex); + + return dev; +} +EXPORT_SYMBOL(b53_switch_alloc); + +int b53_switch_detect(struct b53_device *dev) +{ + u32 id32; + u16 tmp; + u8 id8; + int ret; + + ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); + if (ret) + return ret; + + switch (id8) { + case 0: + /* + * BCM5325 and BCM5365 do not have this register so reads + * return 0. But the read operation did succeed, so assume + * this is one of them. + * + * Next check if we can write to the 5325's VTA register; for + * 5365 it is read only. + */ + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); + b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); + + if (tmp == 0xf) + dev->chip_id = BCM5325_DEVICE_ID; + else + dev->chip_id = BCM5365_DEVICE_ID; + break; + case BCM5395_DEVICE_ID: + case BCM5397_DEVICE_ID: + case BCM5398_DEVICE_ID: + dev->chip_id = id8; + break; + default: + ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); + if (ret) + return ret; + + switch (id32) { + case BCM53115_DEVICE_ID: + case BCM53125_DEVICE_ID: + case BCM53128_DEVICE_ID: + case BCM53010_DEVICE_ID: + case BCM53011_DEVICE_ID: + case BCM53012_DEVICE_ID: + case BCM53018_DEVICE_ID: + case BCM53019_DEVICE_ID: + dev->chip_id = id32; + break; + default: + pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", + id8, id32); + return -ENODEV; + } + } + + if (dev->chip_id == BCM5325_DEVICE_ID) + return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, + &dev->core_rev); + else + return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, + &dev->core_rev); +} +EXPORT_SYMBOL(b53_switch_detect); + +int b53_switch_register(struct b53_device *dev) +{ + int ret; + + if (dev->pdata) { + dev->chip_id = dev->pdata->chip_id; + dev->enabled_ports = dev->pdata->enabled_ports; + dev->sw_dev.alias = dev->pdata->alias; + } + + if (!dev->chip_id && b53_switch_detect(dev)) + return -EINVAL; + + ret = b53_switch_init(dev); + if (ret) + return ret; + + pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); + + return register_switch(&dev->sw_dev, NULL); +} +EXPORT_SYMBOL(b53_switch_register); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 switch library"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mdio.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mdio.c new file mode 100644 index 0000000000..75bb4d941c --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mdio.c @@ -0,0 +1,436 @@ +/* + * B53 register access through MII registers + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "b53_priv.h" + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +/* MII registers */ +#define REG_MII_PAGE 0x10 /* MII Page register */ +#define REG_MII_ADDR 0x11 /* MII Address register */ +#define REG_MII_DATA0 0x18 /* MII Data register 0 */ +#define REG_MII_DATA1 0x19 /* MII Data register 1 */ +#define REG_MII_DATA2 0x1a /* MII Data register 2 */ +#define REG_MII_DATA3 0x1b /* MII Data register 3 */ + +#define REG_MII_PAGE_ENABLE BIT(0) +#define REG_MII_ADDR_WRITE BIT(0) +#define REG_MII_ADDR_READ BIT(1) + +static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) +{ + int i; + u16 v; + int ret; + struct mii_bus *bus = dev->priv; + + if (dev->current_page != page) { + /* set page number */ + v = (page << 8) | REG_MII_PAGE_ENABLE; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); + if (ret) + return ret; + dev->current_page = page; + } + + /* set register address */ + v = (reg << 8) | op; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); + if (ret) + return ret; + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); + if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; + + return 0; +} + +static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + + return 0; +} + +static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; + + return 0; +} + +static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 2; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 3; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u32 temp = value; + + for (i = 0; i < 2; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 3; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 4; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg, + u16 *value) +{ + struct mii_bus *bus = dev->priv; + + *value = mdiobus_read(bus, addr, reg); + + return 0; +} + +static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + + return mdiobus_write(bus, addr, reg, value); +} + +static struct b53_io_ops b53_mdio_ops = { + .read8 = b53_mdio_read8, + .read16 = b53_mdio_read16, + .read32 = b53_mdio_read32, + .read48 = b53_mdio_read48, + .read64 = b53_mdio_read64, + .write8 = b53_mdio_write8, + .write16 = b53_mdio_write16, + .write32 = b53_mdio_write32, + .write48 = b53_mdio_write48, + .write64 = b53_mdio_write64, + .phy_read16 = b53_mdio_phy_read16, + .phy_write16 = b53_mdio_phy_write16, +}; + +static int b53_phy_probe(struct phy_device *phydev) +{ + struct b53_device dev; + int ret; + + /* allow the generic phy driver to take over */ + if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0) + return -ENODEV; + + dev.current_page = 0xff; + dev.priv = phydev->mdio.bus; + dev.ops = &b53_mdio_ops; + dev.pdata = NULL; + mutex_init(&dev.reg_mutex); + + ret = b53_switch_detect(&dev); + if (ret) + return ret; + + if (is5325(&dev) || is5365(&dev)) + phydev->supported = SUPPORTED_100baseT_Full; + else + phydev->supported = SUPPORTED_1000baseT_Full; + + phydev->advertising = phydev->supported; + + return 0; +} + +static int b53_phy_config_init(struct phy_device *phydev) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus); + if (!dev) + return -ENOMEM; + + /* we don't use page 0xff, so force a page set */ + dev->current_page = 0xff; + /* force the ethX as alias */ + dev->sw_dev.alias = phydev->attached_dev->name; + + ret = b53_switch_register(dev); + if (ret) { + dev_err(dev->dev, "failed to register switch: %i\n", ret); + return ret; + } + + phydev->priv = dev; + + return 0; +} + +static void b53_phy_remove(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (!priv) + return; + + b53_switch_remove(priv); + + phydev->priv = NULL; +} + +static int b53_phy_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int b53_phy_read_status(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (is5325(priv) || is5365(priv)) + phydev->speed = 100; + else + phydev->speed = 1000; + + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + phydev->state = PHY_RUNNING; + + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* BCM5325, BCM539x */ +static struct phy_driver b53_phy_driver_id1 = { + .phy_id = 0x0143bc00, + .name = "Broadcom B53 (1)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM53125, BCM53128 */ +static struct phy_driver b53_phy_driver_id2 = { + .phy_id = 0x03625c00, + .name = "Broadcom B53 (2)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM5365 */ +static struct phy_driver b53_phy_driver_id3 = { + .phy_id = 0x00406000, + .name = "Broadcom B53 (3)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +int __init b53_phy_driver_register(void) +{ + int ret; + + ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE); + if (ret) + return ret; + + ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE); + if (ret) + goto err1; + + ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE); + if (!ret) + return 0; + + phy_driver_unregister(&b53_phy_driver_id2); +err1: + phy_driver_unregister(&b53_phy_driver_id1); + return ret; +} + +void __exit b53_phy_driver_unregister(void) +{ + phy_driver_unregister(&b53_phy_driver_id3); + phy_driver_unregister(&b53_phy_driver_id2); + phy_driver_unregister(&b53_phy_driver_id1); +} + +module_init(b53_phy_driver_register); +module_exit(b53_phy_driver_unregister); + +MODULE_DESCRIPTION("B53 MDIO access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mmap.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mmap.c new file mode 100644 index 0000000000..ab1895e9b5 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_mmap.c @@ -0,0 +1,241 @@ +/* + * B53 register access through memory mapped registers + * + * Copyright (C) 2012-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + + *val = readb(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readw_be(regs + (page << 8) + reg); + else + *val = readw(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readl_be(regs + (page << 8) + reg); + else + *val = readl(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u16 lo; + u32 hi; + + b53_mmap_read16(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 2, &hi); + + *val = ((u64)hi << 16) | lo; + } else { + u32 lo; + u16 hi; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read16(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + } + + return 0; +} + +static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u32 hi, lo; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + + return 0; +} + +static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + + writeb(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writew_be(value, regs + (page << 8) + reg); + else + writew(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writel_be(value, regs + (page << 8) + reg); + else + writel(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u32 hi = (u32)(value >> 16); + u16 lo = (u16)value; + + b53_mmap_write16(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 2, hi); + } else { + u16 hi = (u16)(value >> 32); + u32 lo = (u32)value; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write16(dev, page, reg + 4, hi); + } + + return 0; +} + +static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u32 hi, lo; + + hi = (u32)(value >> 32); + lo = (u32)value; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 4, hi); + + return 0; +} + +static struct b53_io_ops b53_mmap_ops = { + .read8 = b53_mmap_read8, + .read16 = b53_mmap_read16, + .read32 = b53_mmap_read32, + .read48 = b53_mmap_read48, + .read64 = b53_mmap_read64, + .write8 = b53_mmap_write8, + .write16 = b53_mmap_write16, + .write32 = b53_mmap_write32, + .write48 = b53_mmap_write48, + .write64 = b53_mmap_write64, +}; + +static int b53_mmap_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_mmap_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_mmap_driver = { + .probe = b53_mmap_probe, + .remove = b53_mmap_remove, + .driver = { + .name = "b53-switch", + }, +}; + +module_platform_driver(b53_mmap_driver); +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 MMAP access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_phy_fixup.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_phy_fixup.c new file mode 100644 index 0000000000..e2f8a39ab6 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_phy_fixup.c @@ -0,0 +1,55 @@ +/* + * B53 PHY Fixup call + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +#define B53_BRCM_OUI_1 0x0143bc00 +#define B53_BRCM_OUI_2 0x03625c00 +#define B53_BRCM_OUI_3 0x00406000 + +static int b53_phy_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u32 phy_id; + + if (dev->mdio.addr != B53_PSEUDO_PHY) + return 0; + + /* read the first port's id */ + phy_id = mdiobus_read(bus, 0, 2) << 16; + phy_id |= mdiobus_read(bus, 0, 3); + + if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) { + dev->phy_id = phy_id; + } + + return 0; +} + +int __init b53_phy_fixup_register(void) +{ + return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup); +} + +subsys_initcall(b53_phy_fixup_register); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_priv.h b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_priv.h new file mode 100644 index 0000000000..a9296c9421 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_priv.h @@ -0,0 +1,341 @@ +/* + * B53 common definitions + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_PRIV_H +#define __B53_PRIV_H + +#include +#include +#include + +struct b53_device; + +struct b53_io_ops { + int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); + int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); + int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); + int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); + int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); + int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); + int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value); +}; + +enum { + BCM5325_DEVICE_ID = 0x25, + BCM5365_DEVICE_ID = 0x65, + BCM5395_DEVICE_ID = 0x95, + BCM5397_DEVICE_ID = 0x97, + BCM5398_DEVICE_ID = 0x98, + BCM53115_DEVICE_ID = 0x53115, + BCM53125_DEVICE_ID = 0x53125, + BCM53128_DEVICE_ID = 0x53128, + BCM63XX_DEVICE_ID = 0x6300, + BCM53010_DEVICE_ID = 0x53010, + BCM53011_DEVICE_ID = 0x53011, + BCM53012_DEVICE_ID = 0x53012, + BCM53018_DEVICE_ID = 0x53018, + BCM53019_DEVICE_ID = 0x53019, +}; + +#define B53_N_PORTS 9 +#define B53_N_PORTS_25 6 + +struct b53_vlan { + unsigned int members:B53_N_PORTS; + unsigned int untag:B53_N_PORTS; +}; + +struct b53_port { + unsigned int pvid:12; +}; + +struct b53_device { + struct switch_dev sw_dev; + struct b53_platform_data *pdata; + + struct mutex reg_mutex; + const struct b53_io_ops *ops; + + /* chip specific data */ + u32 chip_id; + u8 core_rev; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + int reset_gpio; + + /* used ports mask */ + u16 enabled_ports; + + /* connect specific data */ + u8 current_page; + struct device *dev; + void *priv; + + /* run time configuration */ + unsigned enable_vlan:1; + unsigned enable_jumbo:1; + unsigned allow_vid_4095:1; + + struct b53_port *ports; + struct b53_vlan *vlans; + + char *buf; +}; + +#define b53_for_each_port(dev, i) \ + for (i = 0; i < B53_N_PORTS; i++) \ + if (dev->enabled_ports & BIT(i)) + + + +static inline int is5325(struct b53_device *dev) +{ + return dev->chip_id == BCM5325_DEVICE_ID; +} + +static inline int is5365(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + return dev->chip_id == BCM5365_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5397_98(struct b53_device *dev) +{ + return dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is539x(struct b53_device *dev) +{ + return dev->chip_id == BCM5395_DEVICE_ID || + dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is531x5(struct b53_device *dev) +{ + return dev->chip_id == BCM53115_DEVICE_ID || + dev->chip_id == BCM53125_DEVICE_ID || + dev->chip_id == BCM53128_DEVICE_ID; +} + +static inline int is63xx(struct b53_device *dev) +{ +#ifdef CONFIG_BCM63XX + return dev->chip_id == BCM63XX_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5301x(struct b53_device *dev) +{ + return dev->chip_id == BCM53010_DEVICE_ID || + dev->chip_id == BCM53011_DEVICE_ID || + dev->chip_id == BCM53012_DEVICE_ID || + dev->chip_id == BCM53018_DEVICE_ID || + dev->chip_id == BCM53019_DEVICE_ID; +} + +#define B53_CPU_PORT_25 5 +#define B53_CPU_PORT 8 + +static inline int is_cpu_port(struct b53_device *dev, int port) +{ + return dev->sw_dev.cpu_port == port; +} + +static inline int is_imp_port(struct b53_device *dev, int port) +{ + if (is5325(dev) || is5365(dev)) + return port == B53_CPU_PORT_25; + else + return port == B53_CPU_PORT; +} + +static inline struct b53_device *sw_to_b53(struct switch_dev *sw) +{ + return container_of(sw, struct b53_device, sw_dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv); + +int b53_switch_detect(struct b53_device *dev); + +int b53_switch_register(struct b53_device *dev); + +static inline void b53_switch_remove(struct b53_device *dev) +{ + unregister_switch(&dev->sw_dev); +} + +static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read8(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read16(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read32(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read48(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read64(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write8(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write16(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write32(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write48(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write64(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +#ifdef CONFIG_BCM47XX +#include +#endif + +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) +#include +#endif +static inline int b53_switch_get_reset_gpio(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + enum bcm47xx_board board = bcm47xx_board_get(); + + switch (board) { + case BCM47XX_BOARD_LINKSYS_WRT300NV11: + case BCM47XX_BOARD_LINKSYS_WRT310NV1: + return 8; + default: + break; + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + return bcm47xx_nvram_gpio_pin("robo_reset"); +#else + return -ENOENT; +#endif +} + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_regs.h b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_regs.h new file mode 100644 index 0000000000..f0bf6744a7 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_regs.h @@ -0,0 +1,348 @@ +/* + * B53 register definitions + * + * Copyright (C) 2004 Broadcom Corporation + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_REGS_H +#define __B53_REGS_H + +/* Management Port (SMP) Page offsets */ +#define B53_CTRL_PAGE 0x00 /* Control */ +#define B53_STAT_PAGE 0x01 /* Status */ +#define B53_MGMT_PAGE 0x02 /* Management Mode */ +#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ +#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ +#define B53_ARLIO_PAGE 0x05 /* ARL Access */ +#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ +#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ + +/* PHY Registers */ +#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */ +#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ +#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ + +/* MIB registers */ +#define B53_MIB_PAGE(i) (0x20 + (i)) + +/* Quality of Service (QoS) Registers */ +#define B53_QOS_PAGE 0x30 + +/* Port VLAN Page */ +#define B53_PVLAN_PAGE 0x31 + +/* VLAN Registers */ +#define B53_VLAN_PAGE 0x34 + +/* Jumbo Frame Registers */ +#define B53_JUMBO_PAGE 0x40 + +/* CFP Configuration Registers Page */ +#define B53_CFP_PAGE 0xa1 + +/************************************************************************* + * Control Page registers + *************************************************************************/ + +/* Port Control Register (8 bit) */ +#define B53_PORT_CTRL(i) (0x00 + (i)) +#define PORT_CTRL_RX_DISABLE BIT(0) +#define PORT_CTRL_TX_DISABLE BIT(1) +#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ +#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ +#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ +#define PORT_CTRL_STP_STATE_S 5 +#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S) + +/* SMP Control Register (8 bit) */ +#define B53_SMP_CTRL 0x0a + +/* Switch Mode Control Register (8 bit) */ +#define B53_SWITCH_MODE 0x0b +#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ +#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ + +/* IMP Port state override register (8 bit) */ +#define B53_PORT_OVERRIDE_CTRL 0x0e +#define PORT_OVERRIDE_LINK BIT(0) +#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define PORT_OVERRIDE_SPEED_S 2 +#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ +#define PORT_OVERRIDE_RX_FLOW BIT(4) +#define PORT_OVERRIDE_TX_FLOW BIT(5) +#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */ +#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ + +/* Power-down mode control */ +#define B53_PD_MODE_CTRL_25 0x0f + +/* IP Multicast control (8 bit) */ +#define B53_IP_MULTICAST_CTRL 0x21 +#define B53_IPMC_FWD_EN BIT(1) +#define B53_UC_FWD_EN BIT(6) +#define B53_MC_FWD_EN BIT(7) + +/* (16 bit) */ +#define B53_UC_FLOOD_MASK 0x32 +#define B53_MC_FLOOD_MASK 0x34 +#define B53_IPMC_FLOOD_MASK 0x36 + +/* + * Override Ports 0-7 State on devices with xMII interfaces (8 bit) + * + * For port 8 still use B53_PORT_OVERRIDE_CTRL + * Please note that not all ports are available on every hardware, e.g. BCM5301X + * don't include overriding port 6, BCM63xx also have some limitations. + */ +#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i)) +#define GMII_PO_LINK BIT(0) +#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define GMII_PO_SPEED_S 2 +#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S) +#define GMII_PO_RX_FLOW BIT(4) +#define GMII_PO_TX_FLOW BIT(5) +#define GMII_PO_EN BIT(6) /* Use the register contents */ +#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */ + +/* Software reset register (8 bit) */ +#define B53_SOFTRESET 0x79 + +/* Fast Aging Control register (8 bit) */ +#define B53_FAST_AGE_CTRL 0x88 +#define FAST_AGE_STATIC BIT(0) +#define FAST_AGE_DYNAMIC BIT(1) +#define FAST_AGE_PORT BIT(2) +#define FAST_AGE_VLAN BIT(3) +#define FAST_AGE_STP BIT(4) +#define FAST_AGE_MC BIT(5) +#define FAST_AGE_DONE BIT(7) + +/************************************************************************* + * Status Page registers + *************************************************************************/ + +/* Link Status Summary Register (16bit) */ +#define B53_LINK_STAT 0x00 + +/* Link Status Change Register (16 bit) */ +#define B53_LINK_STAT_CHANGE 0x02 + +/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ +#define B53_SPEED_STAT 0x04 +#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) +#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) +#define SPEED_STAT_10M 0 +#define SPEED_STAT_100M 1 +#define SPEED_STAT_1000M 2 + +/* Duplex Status Summary (16 bit) */ +#define B53_DUPLEX_STAT_FE 0x06 +#define B53_DUPLEX_STAT_GE 0x08 +#define B53_DUPLEX_STAT_63XX 0x0c + +/* Revision ID register for BCM5325 */ +#define B53_REV_ID_25 0x50 + +/* Strap Value (48 bit) */ +#define B53_STRAP_VALUE 0x70 +#define SV_GMII_CTRL_115 BIT(27) + +/************************************************************************* + * Management Mode Page Registers + *************************************************************************/ + +/* Global Management Config Register (8 bit) */ +#define B53_GLOBAL_CONFIG 0x00 +#define GC_RESET_MIB 0x01 +#define GC_RX_BPDU_EN 0x02 +#define GC_MIB_AC_HDR_EN 0x10 +#define GC_MIB_AC_EN 0x20 +#define GC_FRM_MGMT_PORT_M 0xC0 +#define GC_FRM_MGMT_PORT_04 0x00 +#define GC_FRM_MGMT_PORT_MII 0x80 + +/* Broadcom Header control register (8 bit) */ +#define B53_BRCM_HDR 0x03 +#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */ +#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */ + +/* Device ID register (8 or 32 bit) */ +#define B53_DEVICE_ID 0x30 + +/* Revision ID register (8 bit) */ +#define B53_REV_ID 0x40 + +/************************************************************************* + * ARL Access Page Registers + *************************************************************************/ + +/* VLAN Table Access Register (8 bit) */ +#define B53_VT_ACCESS 0x80 +#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ +#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ +#define VTA_CMD_WRITE 0 +#define VTA_CMD_READ 1 +#define VTA_CMD_CLEAR 2 +#define VTA_START_CMD BIT(7) + +/* VLAN Table Index Register (16 bit) */ +#define B53_VT_INDEX 0x81 +#define B53_VT_INDEX_9798 0x61 +#define B53_VT_INDEX_63XX 0x62 + +/* VLAN Table Entry Register (32 bit) */ +#define B53_VT_ENTRY 0x83 +#define B53_VT_ENTRY_9798 0x63 +#define B53_VT_ENTRY_63XX 0x64 +#define VTE_MEMBERS 0x1ff +#define VTE_UNTAG_S 9 +#define VTE_UNTAG (0x1ff << 9) + +/************************************************************************* + * Port VLAN Registers + *************************************************************************/ + +/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ +#define B53_PVLAN_PORT_MASK(i) ((i) * 2) + +/************************************************************************* + * 802.1Q Page Registers + *************************************************************************/ + +/* Global QoS Control (8 bit) */ +#define B53_QOS_GLOBAL_CTL 0x00 + +/* Enable 802.1Q for individual Ports (16 bit) */ +#define B53_802_1P_EN 0x04 + +/************************************************************************* + * VLAN Page Registers + *************************************************************************/ + +/* VLAN Control 0 (8 bit) */ +#define B53_VLAN_CTRL0 0x00 +#define VC0_8021PF_CTRL_MASK 0x3 +#define VC0_8021PF_CTRL_NONE 0x0 +#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021PF_CTRL_CHANGE_VID 0x2 +#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 +#define VC0_8021QF_CTRL_MASK 0xc +#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021QF_CTRL_CHANGE_VID 0x2 +#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 +#define VC0_RESERVED_1 BIT(1) +#define VC0_DROP_VID_MISS BIT(4) +#define VC0_VID_HASH_VID BIT(5) +#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ +#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ + +/* VLAN Control 1 (8 bit) */ +#define B53_VLAN_CTRL1 0x01 +#define VC1_RX_MCST_TAG_EN BIT(1) +#define VC1_RX_MCST_FWD_EN BIT(2) +#define VC1_RX_MCST_UNTAG_EN BIT(3) + +/* VLAN Control 2 (8 bit) */ +#define B53_VLAN_CTRL2 0x02 + +/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ +#define B53_VLAN_CTRL3 0x03 +#define B53_VLAN_CTRL3_63XX 0x04 +#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ +#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ + +/* VLAN Control 4 (8 bit) */ +#define B53_VLAN_CTRL4 0x05 +#define B53_VLAN_CTRL4_25 0x04 +#define B53_VLAN_CTRL4_63XX 0x06 +#define VC4_ING_VID_CHECK_S 6 +#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) +#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ +#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ +#define VC4_NO_ING_VID_CHK 2 /* do not check */ +#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ + +/* VLAN Control 5 (8 bit) */ +#define B53_VLAN_CTRL5 0x06 +#define B53_VLAN_CTRL5_25 0x05 +#define B53_VLAN_CTRL5_63XX 0x07 +#define VC5_VID_FFF_EN BIT(2) +#define VC5_DROP_VTABLE_MISS BIT(3) + +/* VLAN Control 6 (8 bit) */ +#define B53_VLAN_CTRL6 0x07 +#define B53_VLAN_CTRL6_63XX 0x08 + +/* VLAN Table Access Register (16 bit) */ +#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ +#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ +#define VTA_VID_LOW_MASK_25 0xf +#define VTA_VID_LOW_MASK_65 0xff +#define VTA_VID_HIGH_S_25 4 +#define VTA_VID_HIGH_S_65 8 +#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) +#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) +#define VTA_RW_STATE BIT(12) +#define VTA_RW_STATE_RD 0 +#define VTA_RW_STATE_WR BIT(12) +#define VTA_RW_OP_EN BIT(13) + +/* VLAN Read/Write Registers for (16/32 bit) */ +#define B53_VLAN_WRITE_25 0x08 +#define B53_VLAN_WRITE_65 0x0a +#define B53_VLAN_READ 0x0c +#define VA_MEMBER_MASK 0x3f +#define VA_UNTAG_S_25 6 +#define VA_UNTAG_MASK_25 0x3f +#define VA_UNTAG_S_65 7 +#define VA_UNTAG_MASK_65 0x1f +#define VA_VID_HIGH_S 12 +#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) +#define VA_VALID_25 BIT(20) +#define VA_VALID_25_R4 BIT(24) +#define VA_VALID_65 BIT(14) + +/* VLAN Port Default Tag (16 bit) */ +#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) + +/************************************************************************* + * Jumbo Frame Page Registers + *************************************************************************/ + +/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ +#define B53_JUMBO_PORT_MASK 0x01 +#define B53_JUMBO_PORT_MASK_63XX 0x04 +#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ + +/* Good Frame Max Size without 802.1Q TAG (16 bit) */ +#define B53_JUMBO_MAX_SIZE 0x05 +#define B53_JUMBO_MAX_SIZE_63XX 0x08 +#define JMS_MIN_SIZE 1518 +#define JMS_MAX_SIZE 9724 + +/************************************************************************* + * CFP Configuration Page Registers + *************************************************************************/ + +/* CFP Control Register with ports map (8 bit) */ +#define B53_CFP_CTRL 0x00 + +#endif /* !__B53_REGS_H */ diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_spi.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_spi.c new file mode 100644 index 0000000000..efc8f7ee89 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_spi.c @@ -0,0 +1,344 @@ +/* + * B53 register access through SPI + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +#define B53_SPI_DATA 0xf0 + +#define B53_SPI_STATUS 0xfe +#define B53_SPI_CMD_SPIF BIT(7) +#define B53_SPI_CMD_RACK BIT(5) + +#define B53_SPI_CMD_READ 0x00 +#define B53_SPI_CMD_WRITE 0x01 +#define B53_SPI_CMD_NORMAL 0x60 +#define B53_SPI_CMD_FAST 0x10 + +#define B53_SPI_PAGE_SELECT 0xff + +static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, + unsigned len) +{ + u8 txbuf[2]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; + txbuf[1] = reg; + + return spi_write_then_read(spi, txbuf, 2, val, len); +} + +static inline int b53_spi_clear_status(struct spi_device *spi) +{ + unsigned int i; + u8 rxbuf; + int ret; + + for (i = 0; i < 10; i++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (!(rxbuf & B53_SPI_CMD_SPIF)) + break; + + mdelay(1); + } + + if (i == 10) + return -EIO; + + return 0; +} + +static inline int b53_spi_set_page(struct spi_device *spi, u8 page) +{ + u8 txbuf[3]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = B53_SPI_PAGE_SELECT; + txbuf[2] = page; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) +{ + int ret = b53_spi_clear_status(spi); + + if (ret) + return ret; + + return b53_spi_set_page(spi, page); +} + +static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) +{ + u8 rxbuf; + int retry_count; + int ret; + + ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); + if (ret) + return ret; + + for (retry_count = 0; retry_count < 10; retry_count++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (rxbuf & B53_SPI_CMD_RACK) + break; + + mdelay(1); + } + + if (retry_count == 10) + return -EIO; + + return 0; +} + +static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, + unsigned len) +{ + struct spi_device *spi = dev->priv; + int ret; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + ret = b53_spi_prepare_reg_read(spi, reg); + if (ret) + return ret; + + return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); +} + +static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + return b53_spi_read(dev, page, reg, val, 1); +} + +static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); + + if (!ret) + *val = le16_to_cpu(*val); + + return ret; +} + +static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); + + if (!ret) + *val = le32_to_cpu(*val); + + return ret; +} + +static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + *val = 0; + ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); + + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[3]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + txbuf[2] = value; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[4]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le16(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[6]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le32(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf) - 2); +} + +static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static struct b53_io_ops b53_spi_ops = { + .read8 = b53_spi_read8, + .read16 = b53_spi_read16, + .read32 = b53_spi_read32, + .read48 = b53_spi_read48, + .read64 = b53_spi_read64, + .write8 = b53_spi_write8, + .write16 = b53_spi_write16, + .write32 = b53_spi_write32, + .write48 = b53_spi_write48, + .write64 = b53_spi_write64, +}; + +static int b53_spi_probe(struct spi_device *spi) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); + if (!dev) + return -ENOMEM; + + if (spi->dev.platform_data) + dev->pdata = spi->dev.platform_data; + + ret = b53_switch_register(dev); + if (ret) + return ret; + + spi_set_drvdata(spi, dev); + + return 0; +} + +static int b53_spi_remove(struct spi_device *spi) +{ + struct b53_device *dev = spi_get_drvdata(spi); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static const struct of_device_id b53_of_match[] = { + { .compatible = "brcm,bcm5325" }, + { .compatible = "brcm,bcm53115" }, + { .compatible = "brcm,bcm53125" }, + { .compatible = "brcm,bcm53128" }, + { .compatible = "brcm,bcm5365" }, + { .compatible = "brcm,bcm5395" }, + { .compatible = "brcm,bcm5397" }, + { .compatible = "brcm,bcm5398" }, + { /* sentinel */ }, +}; + +static struct spi_driver b53_spi_driver = { + .driver = { + .name = "b53-switch", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = b53_of_match, + }, + .probe = b53_spi_probe, + .remove = b53_spi_remove, +}; + +module_spi_driver(b53_spi_driver); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 SPI access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_srab.c b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_srab.c new file mode 100644 index 0000000000..012daa3a51 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/b53/b53_srab.c @@ -0,0 +1,378 @@ +/* + * B53 register access through Switch Register Access Bridge Registers + * + * Copyright (C) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +/* command and status register of the SRAB */ +#define B53_SRAB_CMDSTAT 0x2c +#define B53_SRAB_CMDSTAT_RST BIT(2) +#define B53_SRAB_CMDSTAT_WRITE BIT(1) +#define B53_SRAB_CMDSTAT_GORDYN BIT(0) +#define B53_SRAB_CMDSTAT_PAGE 24 +#define B53_SRAB_CMDSTAT_REG 16 + +/* high order word of write data to switch registe */ +#define B53_SRAB_WD_H 0x30 + +/* low order word of write data to switch registe */ +#define B53_SRAB_WD_L 0x34 + +/* high order word of read data from switch register */ +#define B53_SRAB_RD_H 0x38 + +/* low order word of read data from switch register */ +#define B53_SRAB_RD_L 0x3c + +/* command and status register of the SRAB */ +#define B53_SRAB_CTRLS 0x40 +#define B53_SRAB_CTRLS_RCAREQ BIT(3) +#define B53_SRAB_CTRLS_RCAGNT BIT(4) +#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) + +/* the register captures interrupt pulses from the switch */ +#define B53_SRAB_INTR 0x44 + +static int b53_srab_request_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + int i; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls |= B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); + + for (i = 0; i < 20; i++) { + ctrls = readl(regs + B53_SRAB_CTRLS); + if (ctrls & B53_SRAB_CTRLS_RCAGNT) + break; + usleep_range(10, 100); + } + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static void b53_srab_release_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls &= ~B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); +} + +static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) +{ + int i; + u32 cmdstat; + u8 __iomem *regs = dev->priv; + + /* set register address */ + cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | + (reg << B53_SRAB_CMDSTAT_REG) | + B53_SRAB_CMDSTAT_GORDYN | + op; + writel(cmdstat, regs + B53_SRAB_CMDSTAT); + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + cmdstat = readl(regs + B53_SRAB_CMDSTAT); + if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xffff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u16)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u32)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static struct b53_io_ops b53_srab_ops = { + .read8 = b53_srab_read8, + .read16 = b53_srab_read16, + .read32 = b53_srab_read32, + .read48 = b53_srab_read48, + .read64 = b53_srab_read64, + .write8 = b53_srab_write8, + .write16 = b53_srab_write16, + .write32 = b53_srab_write32, + .write48 = b53_srab_write48, + .write64 = b53_srab_write64, +}; + +static int b53_srab_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_srab_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_srab_driver = { + .probe = b53_srab_probe, + .remove = b53_srab_remove, + .driver = { + .name = "b53-srab-switch", + }, +}; + +module_platform_driver(b53_srab_driver); +MODULE_AUTHOR("Hauke Mehrtens "); +MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/ip17xx.c b/target/linux/generic/files-4.14/drivers/net/phy/ip17xx.c new file mode 100644 index 0000000000..85a9617a5d --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/ip17xx.c @@ -0,0 +1,1377 @@ +/* + * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family + * + * Copyright (C) 2008 Patrick Horn + * Copyright (C) 2008, 2010 Martin Mares + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_VLANS 16 +#define MAX_PORTS 9 +#undef DUMP_MII_IO + +typedef struct ip17xx_reg { + u16 p; // phy + u16 m; // mii +} reg; +typedef char bitnum; + +#define NOTSUPPORTED {-1,-1} + +#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) + +struct ip17xx_state; + +/*********** CONSTANTS ***********/ +struct register_mappings { + char *NAME; + u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. + bitnum NUM_PORTS; + bitnum CPU_PORT; + +/* The default VLAN for each port. + Default: 0x0001 for Ports 0,1,2,3 + 0x0002 for Ports 4,5 */ + reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; + +/* These ports are tagged. + Default: 0x00 */ + reg ADD_TAG_REG; + reg REMOVE_TAG_REG; + bitnum ADD_TAG_BIT[MAX_PORTS]; +/* These ports are untagged. + Default: 0x00 (i.e. do not alter any VLAN tags...) + Maybe set to 0 if user disables VLANs. */ + bitnum REMOVE_TAG_BIT[MAX_PORTS]; + +/* Port M and Port N are on the same VLAN. + Default: All ports on all VLANs. */ +// Use register {29, 19+N/2} + reg VLAN_LOOKUP_REG; +// Port 5 uses register {30, 18} but same as odd bits. + reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. + bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; + bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; + +/* This VLAN corresponds to which ports. + Default: 0x2f,0x30,0x3f,0x3f... */ + reg TAG_VLAN_MASK_REG; + bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; + bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; + + int RESET_VAL; + reg RESET_REG; + + reg MODE_REG; + int MODE_VAL; + +/* General flags */ + reg ROUTER_CONTROL_REG; + reg VLAN_CONTROL_REG; + bitnum TAG_VLAN_BIT; + bitnum ROUTER_EN_BIT; + bitnum NUMLAN_GROUPS_MAX; + bitnum NUMLAN_GROUPS_BIT; + + reg MII_REGISTER_EN; + bitnum MII_REGISTER_EN_BIT; + + // set to 1 for 178C, 0 for 175C. + bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. + + // Pointers to functions which manipulate hardware state + int (*update_state)(struct ip17xx_state *state); + int (*set_vlan_mode)(struct ip17xx_state *state); + int (*reset)(struct ip17xx_state *state); +}; + +static int ip175c_update_state(struct ip17xx_state *state); +static int ip175c_set_vlan_mode(struct ip17xx_state *state); +static int ip175c_reset(struct ip17xx_state *state); + +static const struct register_mappings IP178C = { + .NAME = "IP178C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, + {30,9},{30,10},{30,11}, + }, + + .ADD_TAG_REG = {30,12}, + .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, + .REMOVE_TAG_REG = {30,13}, + .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, + + .SIMPLE_VLAN_REGISTERS = 1, + + .VLAN_LOOKUP_REG = {31,0},// +N + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS + .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .TAG_VLAN_MASK_REG = {30,14}, // +N + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .RESET_VAL = 0x55AA, + .RESET_REG = {30,0}, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = {30,30}, + .ROUTER_EN_BIT = 11, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 8, // {0-2} + + .VLAN_CONTROL_REG = {30,13}, + .TAG_VLAN_BIT = 3, + + .CPU_PORT = 8, + .NUM_PORTS = 9, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175C = { + .NAME = "IP175C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {29,23}, + .REMOVE_TAG_REG = {29,23}, + .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = {29,19},// +N/2 + .VLAN_LOOKUP_REG_5 = {30,18}, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = {30,1}, // +N/2 + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, + + .RESET_VAL = 0x175C, + .RESET_REG = {30,0}, + .MODE_VAL = 0x175C, + .MODE_REG = {29,31}, + + .ROUTER_CONTROL_REG = {30,9}, + .ROUTER_EN_BIT = 3, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 0, // {0-2} + + .VLAN_CONTROL_REG = {30,9}, + .TAG_VLAN_BIT = 7, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175A = { + .NAME = "IP175A", + .MODEL_NO = 0x05, + .VLAN_DEFAULT_TAG_REG = { + {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {0,23}, + .REMOVE_TAG_REG = {0,23}, + .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + // Only programmable via EEPROM + .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, + .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + + .RESET_VAL = -1, + .RESET_REG = NOTSUPPORTED, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_MAX = -1, + .NUMLAN_GROUPS_BIT = -1, // {0-2} + + .NUM_PORTS = 5, + .CPU_PORT = 4, + + .MII_REGISTER_EN = {0, 18}, + .MII_REGISTER_EN_BIT = 7, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + + +static int ip175d_update_state(struct ip17xx_state *state); +static int ip175d_set_vlan_mode(struct ip17xx_state *state); +static int ip175d_reset(struct ip17xx_state *state); + +static const struct register_mappings IP175D = { + .NAME = "IP175D", + .MODEL_NO = 0x18, + + // The IP175D has a completely different interface, so we leave most + // of the registers undefined and switch to different code paths. + + .VLAN_DEFAULT_TAG_REG = { + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + }, + + .ADD_TAG_REG = NOTSUPPORTED, + .REMOVE_TAG_REG = NOTSUPPORTED, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = NOTSUPPORTED, + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .TAG_VLAN_MASK_REG = NOTSUPPORTED, + + .RESET_VAL = 0x175D, + .RESET_REG = {20,2}, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_BIT = -1, + + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175d_update_state, + .set_vlan_mode = ip175d_set_vlan_mode, + .reset = ip175d_reset, +}; + +struct ip17xx_state { + struct switch_dev dev; + struct mii_bus *mii_bus; + bool registered; + + int router_mode; // ROUTER_EN + int vlan_enabled; // TAG_VLAN_EN + struct port_state { + u16 pvid; + unsigned int shareports; + } ports[MAX_PORTS]; + unsigned int add_tag; + unsigned int remove_tag; + int num_vlans; + struct vlan_state { + unsigned int ports; + unsigned int tag; // VLAN tag (IP175D only) + } vlans[MAX_VLANS]; + const struct register_mappings *regs; + reg proc_mii; // phy/reg for the low level register access via swconfig + + char buf[80]; +}; + +#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) + +static int ip_phy_read(struct ip17xx_state *state, int port, int reg) +{ + int val = mdiobus_read(state->mii_bus, port, reg); + if (val < 0) + pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); +#ifdef DUMP_MII_IO + else + pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); +#endif + return val; +} + +static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) +{ + int err; + +#ifdef DUMP_MII_IO + pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); +#endif + err = mdiobus_write(state->mii_bus, port, reg, val); + if (err < 0) + pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); + return err; +} + +static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) +{ + int val = ip_phy_read(state, port, reg); + if (val < 0) + return 0; + return ip_phy_write(state, port, reg, (val & ~mask) | data); +} + +static int getPhy(struct ip17xx_state *state, reg mii) +{ + if (!REG_SUPP(mii)) + return -EFAULT; + return ip_phy_read(state, mii.p, mii.m); +} + +static int setPhy(struct ip17xx_state *state, reg mii, u16 value) +{ + int err; + + if (!REG_SUPP(mii)) + return -EFAULT; + err = ip_phy_write(state, mii.p, mii.m, value); + if (err < 0) + return err; + mdelay(2); + getPhy(state, mii); + return 0; +} + + +/** + * These two macros are to simplify the mapping of logical bits to the bits in hardware. + * NOTE: these macros will return if there is an error! + */ + +#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + (bits) = 0; \ + for (i = 0; i < MAX_PORTS; i++) { \ + if ((bit_lookup)[i] == -1) continue; \ + if (val & (1<<(bit_lookup)[i])) \ + (bits) |= (1<>i)<<(bit_lookup)[i]); \ + } \ + val = setPhy((state), (addr), val); \ + if (val < 0) \ + return val; \ + } while (0) + + +static int get_model(struct ip17xx_state *state) +{ + int id1, id2; + int oui_id, model_no, rev_no, chip_no; + + id1 = ip_phy_read(state, 0, 2); + id2 = ip_phy_read(state, 0, 3); + oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); + model_no = (id2 >> 4) & 0x3f; + rev_no = id2 & 0xf; + pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); + + if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway + return -ENODEV; + + if (model_no == IP175A.MODEL_NO) { + state->regs = &IP175A; + } else if (model_no == IP175C.MODEL_NO) { + /* + * Several models share the same model_no: + * 178C has more PHYs, so we try whether the device responds to a read from PHY5 + * 175D has a new chip ID register + * 175C has neither + */ + if (ip_phy_read(state, 5, 2) == 0x0243) { + state->regs = &IP178C; + } else { + chip_no = ip_phy_read(state, 20, 0); + pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); + if (chip_no == 0x175d) { + state->regs = &IP175D; + } else { + state->regs = &IP175C; + } + } + } else { + pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); + return -EPERM; + } + return 0; +} + +/*** Low-level functions for the older models ***/ + +/** Only set vlan and router flags in the switch **/ +static int ip175c_set_flags(struct ip17xx_state *state) +{ + int val; + + if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { + return 0; + } + + val = getPhy(state, state->regs->ROUTER_CONTROL_REG); + if (val < 0) { + return val; + } + if (state->regs->ROUTER_EN_BIT >= 0) { + if (state->router_mode) { + val |= (1<regs->ROUTER_EN_BIT); + } else { + val &= (~(1<regs->ROUTER_EN_BIT)); + } + } + if (state->regs->TAG_VLAN_BIT >= 0) { + if (state->vlan_enabled) { + val |= (1<regs->TAG_VLAN_BIT); + } else { + val &= (~(1<regs->TAG_VLAN_BIT)); + } + } + if (state->regs->NUMLAN_GROUPS_BIT >= 0) { + val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<regs->NUMLAN_GROUPS_BIT)); + if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { + val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; + } else if (state->num_vlans >= 1) { + val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; + } + } + return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); +} + +/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ +static int ip175c_set_state(struct ip17xx_state *state) +{ + int j; + int i; + SET_PORT_BITS(state, state->add_tag, + state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); + SET_PORT_BITS(state, state->remove_tag, + state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); + + if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { + for (j=0; jregs->NUM_PORTS; j++) { + reg addr; + const bitnum *bit_lookup = (j%2==0)? + state->regs->VLAN_LOOKUP_EVEN_BIT: + state->regs->VLAN_LOOKUP_ODD_BIT; + + addr = state->regs->VLAN_LOOKUP_REG; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + switch (j) { + case 0: + case 1: + break; + case 2: + case 3: + addr.m+=1; + break; + case 4: + addr.m+=2; + break; + case 5: + addr = state->regs->VLAN_LOOKUP_REG_5; + break; + default: + addr.m = -1; // shouldn't get here, but... + break; + } + } + //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); + if (REG_SUPP(addr)) { + SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); + } + } + } + if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { + for (j=0; jregs->TAG_VLAN_MASK_REG; + const bitnum *bit_lookup = (j%2==0)? + state->regs->TAG_VLAN_MASK_EVEN_BIT: + state->regs->TAG_VLAN_MASK_ODD_BIT; + unsigned int vlan_mask; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + addr.m += j/2; + } + vlan_mask = state->vlans[j].ports; + SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); + } + } + + for (i=0; iregs->VLAN_DEFAULT_TAG_REG[i])) { + int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], + state->ports[i].pvid); + if (err < 0) { + return err; + } + } + } + + return ip175c_set_flags(state); +} + +/** + * Uses only the VLAN port mask and the add tag mask to generate the other fields: + * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. + */ +static void ip175c_correct_vlan_state(struct ip17xx_state *state) +{ + int i, j; + state->num_vlans = 0; + for (i=0; ivlans[i].ports != 0) { + state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... + } + } + + for (i=0; iregs->NUM_PORTS; i++) { + unsigned int portmask = (1<vlan_enabled) { + // Share with everybody! + state->ports[i].shareports = (1<regs->NUM_PORTS)-1; + continue; + } + state->ports[i].shareports = portmask; + for (j=0; jvlans[j].ports & portmask) + state->ports[i].shareports |= state->vlans[j].ports; + } + } +} + +static int ip175c_update_state(struct ip17xx_state *state) +{ + ip175c_correct_vlan_state(state); + return ip175c_set_state(state); +} + +static int ip175c_set_vlan_mode(struct ip17xx_state *state) +{ + return ip175c_update_state(state); +} + +static int ip175c_reset(struct ip17xx_state *state) +{ + int err; + + if (REG_SUPP(state->regs->MODE_REG)) { + err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->MODE_REG); + if (err < 0) + return err; + } + + return ip175c_update_state(state); +} + +/*** Low-level functions for IP175D ***/ + +static int ip175d_update_state(struct ip17xx_state *state) +{ + unsigned int filter_mask = 0; + unsigned int ports[16], add[16], rem[16]; + int i, j; + int err = 0; + + for (i = 0; i < 16; i++) { + ports[i] = 0; + add[i] = 0; + rem[i] = 0; + if (!state->vlan_enabled) { + err |= ip_phy_write(state, 22, 14+i, i+1); // default tags + ports[i] = 0x3f; + continue; + } + if (!state->vlans[i].tag) { + // Reset the filter + err |= ip_phy_write(state, 22, 14+i, 0); // tag + continue; + } + filter_mask |= 1 << i; + err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); + ports[i] = state->vlans[i].ports; + for (j = 0; j < 6; j++) { + if (ports[i] & (1 << j)) { + if (state->add_tag & (1 << j)) + add[i] |= 1 << j; + if (state->remove_tag & (1 << j)) + rem[i] |= 1 << j; + } + } + } + + // Port masks, tag adds and removals + for (i = 0; i < 8; i++) { + err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); + } + err |= ip_phy_write(state, 22, 10, filter_mask); + + // Default VLAN tag for each port + for (i = 0; i < 6; i++) + err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); + + return (err ? -EIO : 0); +} + +static int ip175d_set_vlan_mode(struct ip17xx_state *state) +{ + int i; + int err = 0; + + if (state->vlan_enabled) { + // VLAN classification rules: tag-based VLANs, use VID to classify, + // drop packets that cannot be classified. + err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); + + // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, + // VID=0xfff discarded, admin both tagged and untagged, ingress + // filters enabled. + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + + // Egress rules: IGMP processing off, keep VLAN header off + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } else { + // VLAN classification rules: everything off & clear table + err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); + + // Ingress and egress rules: set to defaults + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } + + // Reset default VLAN for each port to 0 + for (i = 0; i < 6; i++) + state->ports[i].pvid = 0; + + err |= ip175d_update_state(state); + + return (err ? -EIO : 0); +} + +static int ip175d_reset(struct ip17xx_state *state) +{ + int err = 0; + + // Disable the special tagging mode + err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); + + // Set 802.1q protocol type + err |= ip_phy_write(state, 22, 3, 0x8100); + + state->vlan_enabled = 0; + err |= ip175d_set_vlan_mode(state); + + return (err ? -EIO : 0); +} + +/*** High-level functions ***/ + +static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + return 0; +} + +static void ip17xx_reset_vlan_config(struct ip17xx_state *state) +{ + int i; + + state->remove_tag = (state->vlan_enabled ? ((1<regs->NUM_PORTS)-1) : 0x0000); + state->add_tag = 0x0000; + for (i = 0; i < MAX_VLANS; i++) { + state->vlans[i].ports = 0x0000; + state->vlans[i].tag = (i ? i : 16); + } + for (i = 0; i < MAX_PORTS; i++) + state->ports[i].pvid = 0; +} + +static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int enable; + + enable = val->value.i; + if (state->vlan_enabled == enable) { + // Do not change any state. + return 0; + } + state->vlan_enabled = enable; + + // Otherwise, if we are switching state, set fields to a known default. + ip17xx_reset_vlan_config(state); + + return state->regs->set_vlan_mode(state); +} + +static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int b; + int ind; + unsigned int ports; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + ports = state->vlans[val->port_vlan].ports; + b = 0; + ind = 0; + while (b < MAX_PORTS) { + if (ports&1) { + int istagged = ((state->add_tag >> b) & 1); + val->value.ports[ind].id = b; + val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); + ind++; + } + b++; + ports >>= 1; + } + val->len = ind; + + return 0; +} + +static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int i; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + state->vlans[val->port_vlan].ports = 0; + for (i = 0; i < val->len; i++) { + unsigned int bitmask = (1<value.ports[i].id); + state->vlans[val->port_vlan].ports |= bitmask; + if (val->value.ports[i].flags & (1<add_tag |= bitmask; + state->remove_tag &= (~bitmask); + } else { + state->add_tag &= (~bitmask); + state->remove_tag |= bitmask; + } + } + + return state->regs->update_state(state); +} + +static int ip17xx_apply(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + + if (REG_SUPP(state->regs->MII_REGISTER_EN)) { + int val = getPhy(state, state->regs->MII_REGISTER_EN); + if (val < 0) { + return val; + } + val |= (1<regs->MII_REGISTER_EN_BIT); + return setPhy(state, state->regs->MII_REGISTER_EN, val); + } + return 0; +} + +static int ip17xx_reset(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + int i, err; + + if (REG_SUPP(state->regs->RESET_REG)) { + err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->RESET_REG); + + /* + * Data sheet specifies reset period to be 2 msec. + * (I don't see any mention of the 2ms delay in the IP178C spec, only + * in IP175C, but it can't hurt.) + */ + mdelay(2); + } + + /* reset switch ports */ + for (i = 0; i < state->regs->NUM_PORTS-1; i++) { + err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + state->router_mode = 0; + state->vlan_enabled = 0; + ip17xx_reset_vlan_config(state); + + return state->regs->reset(state); +} + +static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + if (state->add_tag & (1<port_vlan)) { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 3; // shouldn't ever happen. + else + val->value.i = 1; + } else { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 0; + else + val->value.i = 2; + } + return 0; +} + +static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + state->add_tag &= ~(1<port_vlan); + state->remove_tag &= ~(1<port_vlan); + + if (val->value.i == 0) + state->remove_tag |= (1<port_vlan); + if (val->value.i == 1) + state->add_tag |= (1<port_vlan); + + return state->regs->update_state(state); +} + +/** Get the current phy address */ +static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.p; + return 0; +} + +/** Set a new phy address for low level access to registers */ +static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.p = (u16)-1; + else + state->proc_mii.p = (u16)new_reg; + return 0; +} + +/** Get the current register number */ +static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.m; + return 0; +} + +/** Set a new register address for low level access to registers */ +static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.m = (u16)-1; + else + state->proc_mii.m = (u16)new_reg; + return 0; +} + +/** Get the register content of state->proc_mii */ +static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int retval = -EINVAL; + if (REG_SUPP(state->proc_mii)) + retval = getPhy(state, state->proc_mii); + + if (retval < 0) { + return retval; + } else { + val->value.i = retval; + return 0; + } +} + +/** Write a value to the register defined by phy/reg above */ +static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int myval, err = -EINVAL; + + myval = val->value.i; + if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { + err = setPhy(state, state->proc_mii, (u16)myval); + } + return err; +} + +static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. + return 0; +} + +static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + val->value.i = state->vlans[vlan].tag; + return 0; +} + +static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + int tag = val->value.i; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + if (tag < 0 || tag > 4095) + return -EINVAL; + + state->vlans[vlan].tag = tag; + return state->regs->update_state(state); +} + +static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int ctrl; + int autoneg; + int speed; + if (val->value.i == 100) { + speed = 1; + autoneg = 0; + } else if (val->value.i == 10) { + speed = 0; + autoneg = 0; + } else { + autoneg = 1; + speed = 1; + } + + /* Can't set speed for cpu port */ + if (nr == state->regs->CPU_PORT) + return -EINVAL; + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + if (ctrl < 0) + return -EIO; + + ctrl &= (~(1<<12)); + ctrl &= (~(1<<13)); + ctrl |= (autoneg<<12); + ctrl |= (speed<<13); + + return ip_phy_write(state, nr, 0, ctrl); +} + +static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int speed, status; + + if (nr == state->regs->CPU_PORT) { + val->value.i = 100; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (status < 0 || speed < 0) + return -EIO; + + if (status & 4) + val->value.i = ((speed & (1<<11)) ? 100 : 10); + else + val->value.i = 0; + + return 0; +} + +static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int ctrl, speed, status; + int nr = val->port_vlan; + int len; + char *buf = state->buf; // fixed-length at 80. + + if (nr == state->regs->CPU_PORT) { + sprintf(buf, "up, 100 Mbps, cpu port"); + val->value.s = buf; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (ctrl < 0 || status < 0 || speed < 0) + return -EIO; + + if (status & 4) + len = sprintf(buf, "up, %d Mbps, %s duplex", + ((speed & (1<<11)) ? 100 : 10), + ((speed & (1<<10)) ? "full" : "half")); + else + len = sprintf(buf, "down"); + + if (ctrl & (1<<12)) { + len += sprintf(buf+len, ", auto-negotiate"); + if (!(status & (1<<5))) + len += sprintf(buf+len, " (in progress)"); + } else { + len += sprintf(buf+len, ", fixed speed (%d)", + ((ctrl & (1<<13)) ? 100 : 10)); + } + + buf[len] = '\0'; + val->value.s = buf; + return 0; +} + +static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct ip17xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + return 0; +} + +static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct ip17xx_state *state = get_state(dev); + + if (val < 0 || val >= MAX_VLANS) + return -EINVAL; + + state->ports[port].pvid = val; + return state->regs->update_state(state); +} + + +enum Ports { + IP17XX_PORT_STATUS, + IP17XX_PORT_LINK, + IP17XX_PORT_TAGGED, + IP17XX_PORT_PVID, +}; + +enum Globals { + IP17XX_ENABLE_VLAN, + IP17XX_GET_NAME, + IP17XX_REGISTER_PHY, + IP17XX_REGISTER_MII, + IP17XX_REGISTER_VALUE, + IP17XX_REGISTER_ERRNO, +}; + +enum Vlans { + IP17XX_VLAN_TAG, +}; + +static const struct switch_attr ip17xx_global[] = { + [IP17XX_ENABLE_VLAN] = { + .id = IP17XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Flag to enable or disable VLANs and tagging", + .get = ip17xx_get_enable_vlan, + .set = ip17xx_set_enable_vlan, + }, + [IP17XX_GET_NAME] = { + .id = IP17XX_GET_NAME, + .type = SWITCH_TYPE_STRING, + .description = "Returns the type of IC+ chip.", + .name = "name", + .get = ip17xx_read_name, + .set = NULL, + }, + /* jal: added for low level debugging etc. */ + [IP17XX_REGISTER_PHY] = { + .id = IP17XX_REGISTER_PHY, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set PHY (0-4, or 29,30,31)", + .name = "phy", + .get = ip17xx_get_phy, + .set = ip17xx_set_phy, + }, + [IP17XX_REGISTER_MII] = { + .id = IP17XX_REGISTER_MII, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set MII register number (0-31)", + .name = "reg", + .get = ip17xx_get_reg, + .set = ip17xx_set_reg, + }, + [IP17XX_REGISTER_VALUE] = { + .id = IP17XX_REGISTER_VALUE, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: read/write to register (0-65535)", + .name = "val", + .get = ip17xx_get_val, + .set = ip17xx_set_val, + }, +}; + +static const struct switch_attr ip17xx_vlan[] = { + [IP17XX_VLAN_TAG] = { + .id = IP17XX_VLAN_TAG, + .type = SWITCH_TYPE_INT, + .description = "VLAN ID (0-4095) [IP175D only]", + .name = "vid", + .get = ip17xx_get_tag, + .set = ip17xx_set_tag, + } +}; + +static const struct switch_attr ip17xx_port[] = { + [IP17XX_PORT_STATUS] = { + .id = IP17XX_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Returns Detailed port status", + .name = "status", + .get = ip17xx_get_port_status, + .set = NULL, + }, + [IP17XX_PORT_LINK] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", + .name = "link", + .get = ip17xx_get_port_speed, + .set = ip17xx_set_port_speed, + }, + [IP17XX_PORT_TAGGED] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", + .name = "tagged", + .get = ip17xx_get_tagged, + .set = ip17xx_set_tagged, + }, +}; + +static const struct switch_dev_ops ip17xx_ops = { + .attr_global = { + .attr = ip17xx_global, + .n_attr = ARRAY_SIZE(ip17xx_global), + }, + .attr_port = { + .attr = ip17xx_port, + .n_attr = ARRAY_SIZE(ip17xx_port), + }, + .attr_vlan = { + .attr = ip17xx_vlan, + .n_attr = ARRAY_SIZE(ip17xx_vlan), + }, + + .get_port_pvid = ip17xx_get_pvid, + .set_port_pvid = ip17xx_set_pvid, + .get_vlan_ports = ip17xx_get_ports, + .set_vlan_ports = ip17xx_set_ports, + .apply_config = ip17xx_apply, + .reset_switch = ip17xx_reset, +}; + +static int ip17xx_probe(struct phy_device *pdev) +{ + struct ip17xx_state *state; + struct switch_dev *dev; + int err; + + /* We only attach to PHY 0, but use all available PHYs */ + if (pdev->mdio.addr != 0) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + dev = &state->dev; + + pdev->priv = state; + state->mii_bus = pdev->mdio.bus; + + err = get_model(state); + if (err < 0) + goto error; + + dev->vlans = MAX_VLANS; + dev->cpu_port = state->regs->CPU_PORT; + dev->ports = state->regs->NUM_PORTS; + dev->name = state->regs->NAME; + dev->ops = &ip17xx_ops; + + pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev)); + return 0; + +error: + kfree(state); + return err; +} + +static int ip17xx_config_init(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + struct net_device *dev = pdev->attached_dev; + int err; + + err = register_switch(&state->dev, dev); + if (err < 0) + return err; + + state->registered = true; + ip17xx_reset(&state->dev); + return 0; +} + +static void ip17xx_remove(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + + if (state->registered) + unregister_switch(&state->dev); + kfree(state); +} + +static int ip17xx_config_aneg(struct phy_device *pdev) +{ + return 0; +} + +static int ip17xx_aneg_done(struct phy_device *pdev) +{ + return 1; /* Return any positive value */ +} + +static int ip17xx_update_link(struct phy_device *pdev) +{ + pdev->link = 1; + return 0; +} + +static int ip17xx_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->pause = pdev->asym_pause = 0; + pdev->link = 1; + + return 0; +} + +static struct phy_driver ip17xx_driver[] = { + { + .name = "IC+ IP17xx", + .phy_id = 0x02430c00, + .phy_id_mask = 0x0ffffc00, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + } +}; + +module_phy_driver(ip17xx_driver); + +MODULE_AUTHOR("Patrick Horn "); +MODULE_AUTHOR("Felix Fietkau "); +MODULE_AUTHOR("Martin Mares "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.c b/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.c new file mode 100644 index 0000000000..9a689e6c81 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.c @@ -0,0 +1,947 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mvsw61xx.h" + +MODULE_DESCRIPTION("Marvell 88E61xx Switch driver"); +MODULE_AUTHOR("Claudio Leite "); +MODULE_AUTHOR("Nikita Nazarenko "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mvsw61xx"); + +/* + * Register access is done through direct or indirect addressing, + * depending on how the switch is physically connected. + * + * Direct addressing: all port and global registers directly + * accessible via an address/register pair + * + * Indirect addressing: switch is mapped at a single address, + * port and global registers accessible via a single command/data + * register pair + */ + +static int +mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = bus->read(bus, addr, reg); + if ((r & mask) == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static u16 +r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) +{ + u16 ind_addr; + + if (!indirect) + return bus->read(bus, addr, reg); + + /* Indirect read: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address and request read */ + ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); + + /* Wait until it's ready */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Read the requested data */ + return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); +} + +static void +w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, + int reg, u16 val) +{ + u16 ind_addr; + + if (!indirect) { + bus->write(bus, addr, reg, val); + return; + } + + /* Indirect write: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load the data to be written */ + bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); + + /* Wait again for switch to be free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address, and issue write command */ + ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); +} + +/* swconfig support */ + +static inline u16 +sr16(struct switch_dev *dev, int addr, int reg) +{ + struct mvsw61xx_state *state = get_state(dev); + + return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); +} + +static inline void +sw16(struct switch_dev *dev, int addr, int reg, u16 val) +{ + struct mvsw61xx_state *state = get_state(dev); + + w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); +} + +static int +mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = sr16(dev, addr, reg) & mask; + if (r == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static int +mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg) +{ + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg); + + if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0) + return -ETIMEDOUT; + + return sr16(dev, MV_GLOBAL2REG(SMI_DATA)); +} + +static int +mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val) +{ + sw16(dev, MV_GLOBAL2REG(SMI_DATA), val); + + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg); + + return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0; +} + +static int +mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg) +{ + int ret; + + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + ret = mvsw61xx_mdio_read(dev, port, reg); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); + + return ret; +} + +static void +mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg, + u16 val) +{ + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + mvsw61xx_mdio_write(dev, port, reg, val); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); +} + +static int +mvsw61xx_get_port_mask(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + char *buf = state->buf; + int port, len, i; + u16 reg; + + port = val->port_vlan; + reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; + + len = sprintf(buf, "0x%04x: ", reg); + + for (i = 0; i < MV_PORTS; i++) { + if (reg & (1 << i)) + len += sprintf(buf + len, "%d ", i); + else if (i == port) + len += sprintf(buf + len, "(%d) ", i); + } + + val->value.s = buf; + + return 0; +} + +static int +mvsw61xx_get_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->ports[val->port_vlan].qmode; + + return 0; +} + +static int +mvsw61xx_set_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->ports[val->port_vlan].qmode = val->value.i; + + return 0; +} + +static int +mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + + return 0; +} + +static int +mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct mvsw61xx_state *state = get_state(dev); + + if (val < 0 || val >= MV_VLANS) + return -EINVAL; + + state->ports[port].pvid = (u16)val; + + return 0; +} + +static int +mvsw61xx_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + u16 status, speed; + + status = sr16(dev, MV_PORTREG(STATUS, port)); + + link->link = status & MV_PORT_STATUS_LINK; + if (!link->link) + return 0; + + link->duplex = status & MV_PORT_STATUS_FDX; + + speed = (status & MV_PORT_STATUS_SPEED_MASK) >> + MV_PORT_STATUS_SPEED_SHIFT; + + switch (speed) { + case MV_PORT_STATUS_SPEED_10: + link->speed = SWITCH_PORT_SPEED_10; + break; + case MV_PORT_STATUS_SPEED_100: + link->speed = SWITCH_PORT_SPEED_100; + break; + case MV_PORT_STATUS_SPEED_1000: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + + return 0; +} + +static int mvsw61xx_get_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, j, mode, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + for (i = 0, j = 0; i < dev->ports; i++) { + if (state->vlans[vno].mask & (1 << i)) { + val->value.ports[j].id = i; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + if (mode == MV_VTUCTL_EGRESS_TAGGED) + val->value.ports[j].flags = + (1 << SWITCH_PORT_FLAG_TAGGED); + else + val->value.ports[j].flags = 0; + + j++; + } + } + + val->len = j; + + return 0; +} + +static int mvsw61xx_set_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode, pno, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].mask = 0; + state->vlans[vno].port_mode = 0; + state->vlans[vno].port_sstate = 0; + + if(state->vlans[vno].vid == 0) + state->vlans[vno].vid = vno; + + for (i = 0; i < val->len; i++) { + pno = val->value.ports[i].id; + + state->vlans[vno].mask |= (1 << pno); + if (val->value.ports[i].flags & + (1 << SWITCH_PORT_FLAG_TAGGED)) + mode = MV_VTUCTL_EGRESS_TAGGED; + else + mode = MV_VTUCTL_EGRESS_UNTAGGED; + + state->vlans[vno].port_mode |= mode << (pno * 4); + state->vlans[vno].port_sstate |= + MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2); + } + + /* + * DISCARD is nonzero, so it must be explicitly + * set on ports not in the VLAN. + */ + for (i = 0; i < dev->ports; i++) + if (!(state->vlans[vno].mask & (1 << i))) + state->vlans[vno].port_mode |= + MV_VTUCTL_DISCARD << (i * 4); + + return 0; +} + +static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (state->vlans[vno].port_based) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (val->value.i == 1) + state->vlans[vno].port_based = true; + else + state->vlans[vno].port_based = false; + + return 0; +} + +static int mvsw61xx_get_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + val->value.i = state->vlans[vno].vid; + + return 0; +} + +static int mvsw61xx_set_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].vid = val->value.i; + + return 0; +} + +static int mvsw61xx_get_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + + return 0; +} + +static int mvsw61xx_set_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->vlan_enabled = val->value.i; + + return 0; +} + +static int mvsw61xx_vtu_program(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + u16 v1, v2, s1, s2; + int i; + + /* Flush */ + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE); + + /* Write VLAN table */ + for (i = 1; i < dev->vlans; i++) { + if (state->vlans[i].mask == 0 || + state->vlans[i].vid == 0 || + state->vlans[i].port_based == true) + continue; + + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write per-VLAN port state into STU */ + s1 = (u16) (state->vlans[i].port_sstate & 0xffff); + s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), s1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), s2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write VLAN information into VTU */ + v1 = (u16) (state->vlans[i].port_mode & 0xffff); + v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), + MV_VTU_VID_VALID | state->vlans[i].vid); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_FID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + } + + return 0; +} + +static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode; + + for (i = 0; i < dev->ports; i++) { + if (!(state->vlans[vno].mask & (1 << i))) + continue; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + + if(mode != MV_VTUCTL_EGRESS_TAGGED) + state->ports[i].pvid = state->vlans[vno].vid; + + if (state->vlans[vno].port_based) { + state->ports[i].mask |= state->vlans[vno].mask; + state->ports[i].fdb = vno; + } + else + state->ports[i].qmode = MV_8021Q_MODE_SECURE; + } +} + +static int mvsw61xx_update_state(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + if (!state->registered) + return -EINVAL; + + /* + * Set 802.1q-only mode if vlan_enabled is true. + * + * Without this, even if 802.1q is enabled for + * a port/VLAN, it still depends on the port-based + * VLAN mask being set. + * + * With this setting, port-based VLANs are still + * functional, provided the VID is not in the VTU. + */ + reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); + + if (state->vlan_enabled) + reg |= MV_8021Q_VLAN_ONLY; + else + reg &= ~MV_8021Q_VLAN_ONLY; + + sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); + + /* + * Set port-based VLAN masks on each port + * based only on VLAN definitions known to + * the driver (i.e. in state). + * + * This means any pre-existing port mapping is + * wiped out once our driver is initialized. + */ + for (i = 0; i < dev->ports; i++) { + state->ports[i].mask = 0; + state->ports[i].qmode = MV_8021Q_MODE_DISABLE; + } + + for (i = 0; i < dev->vlans; i++) + mvsw61xx_vlan_port_config(dev, i); + + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; + reg |= state->ports[i].pvid; + sw16(dev, MV_PORTREG(VLANID, i), reg); + + state->ports[i].mask &= ~(1 << i); + + /* set default forwarding DB number and port mask */ + reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK; + reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) & + MV_FDB_HI_MASK; + sw16(dev, MV_PORTREG(CONTROL1, i), reg); + + reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) | + state->ports[i].mask; + sw16(dev, MV_PORTREG(VLANMAP, i), reg); + + reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & + ~MV_8021Q_MODE_MASK; + reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; + sw16(dev, MV_PORTREG(CONTROL2, i), reg); + } + + mvsw61xx_vtu_program(dev); + + return 0; +} + +static int mvsw61xx_apply(struct switch_dev *dev) +{ + return mvsw61xx_update_state(dev); +} + +static void mvsw61xx_enable_serdes(struct switch_dev *dev) +{ + int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR); + if (bmcr < 0) + return; + + if (bmcr & BMCR_PDOWN) + mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR, + bmcr & ~BMCR_PDOWN); +} + +static int _mvsw61xx_reset(struct switch_dev *dev, bool full) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + /* Disable all ports before reset */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) & + ~MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; + + sw16(dev, MV_GLOBALREG(CONTROL), reg); + if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL), + MV_CONTROL_RESET, 0) < 0) + return -ETIMEDOUT; + + for (i = 0; i < dev->ports; i++) { + state->ports[i].fdb = 0; + state->ports[i].qmode = 0; + state->ports[i].mask = 0; + state->ports[i].pvid = 0; + + /* Force flow control off */ + reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK; + reg |= MV_PHYCTL_FC_DISABLE; + sw16(dev, MV_PORTREG(PHYCTL, i), reg); + + /* Set port association vector */ + sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); + + /* power up phys */ + if (full && i < 5) { + mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL, + MV_SPEC_MDI_CROSS_AUTO | + MV_SPEC_ENERGY_DETECT | + MV_SPEC_DOWNSHIFT_COUNTER); + mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET | + BMCR_ANENABLE | BMCR_FULLDPLX | + BMCR_SPEED1000); + } + + /* enable SerDes if necessary */ + if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) { + u16 sts = sr16(dev, MV_PORTREG(STATUS, i)); + u16 mode = sts & MV_PORT_STATUS_CMODE_MASK; + + if (mode == MV_PORT_STATUS_CMODE_100BASE_X || + mode == MV_PORT_STATUS_CMODE_1000BASE_X || + mode == MV_PORT_STATUS_CMODE_SGMII) { + mvsw61xx_enable_serdes(dev); + } + } + } + + for (i = 0; i < dev->vlans; i++) { + state->vlans[i].port_based = false; + state->vlans[i].mask = 0; + state->vlans[i].vid = 0; + state->vlans[i].port_mode = 0; + state->vlans[i].port_sstate = 0; + } + + state->vlan_enabled = 0; + + mvsw61xx_update_state(dev); + + /* Re-enable ports */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) | + MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + return 0; +} + +static int mvsw61xx_reset(struct switch_dev *dev) +{ + return _mvsw61xx_reset(dev, false); +} + +enum { + MVSW61XX_ENABLE_VLAN, +}; + +enum { + MVSW61XX_VLAN_PORT_BASED, + MVSW61XX_VLAN_ID, +}; + +enum { + MVSW61XX_PORT_MASK, + MVSW61XX_PORT_QMODE, +}; + +static const struct switch_attr mvsw61xx_global[] = { + [MVSW61XX_ENABLE_VLAN] = { + .id = MVSW61XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable 802.1q VLAN support", + .get = mvsw61xx_get_enable_vlan, + .set = mvsw61xx_set_enable_vlan, + }, +}; + +static const struct switch_attr mvsw61xx_vlan[] = { + [MVSW61XX_VLAN_PORT_BASED] = { + .id = MVSW61XX_VLAN_PORT_BASED, + .type = SWITCH_TYPE_INT, + .name = "port_based", + .description = "Use port-based (non-802.1q) VLAN only", + .get = mvsw61xx_get_vlan_port_based, + .set = mvsw61xx_set_vlan_port_based, + }, + [MVSW61XX_VLAN_ID] = { + .id = MVSW61XX_VLAN_ID, + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "Get/set VLAN ID", + .get = mvsw61xx_get_vid, + .set = mvsw61xx_set_vid, + }, +}; + +static const struct switch_attr mvsw61xx_port[] = { + [MVSW61XX_PORT_MASK] = { + .id = MVSW61XX_PORT_MASK, + .type = SWITCH_TYPE_STRING, + .description = "Port-based VLAN mask", + .name = "mask", + .get = mvsw61xx_get_port_mask, + .set = NULL, + }, + [MVSW61XX_PORT_QMODE] = { + .id = MVSW61XX_PORT_QMODE, + .type = SWITCH_TYPE_INT, + .description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", + .name = "qmode", + .get = mvsw61xx_get_port_qmode, + .set = mvsw61xx_set_port_qmode, + }, +}; + +static const struct switch_dev_ops mvsw61xx_ops = { + .attr_global = { + .attr = mvsw61xx_global, + .n_attr = ARRAY_SIZE(mvsw61xx_global), + }, + .attr_vlan = { + .attr = mvsw61xx_vlan, + .n_attr = ARRAY_SIZE(mvsw61xx_vlan), + }, + .attr_port = { + .attr = mvsw61xx_port, + .n_attr = ARRAY_SIZE(mvsw61xx_port), + }, + .get_port_link = mvsw61xx_get_port_link, + .get_port_pvid = mvsw61xx_get_port_pvid, + .set_port_pvid = mvsw61xx_set_port_pvid, + .get_vlan_ports = mvsw61xx_get_vlan_ports, + .set_vlan_ports = mvsw61xx_set_vlan_ports, + .apply_config = mvsw61xx_apply, + .reset_switch = mvsw61xx_reset, +}; + +/* end swconfig stuff */ + +static int mvsw61xx_probe(struct platform_device *pdev) +{ + struct mvsw61xx_state *state; + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio; + char *model_str; + u32 val; + int err; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mdio = of_parse_phandle(np, "mii-bus", 0); + if (!mdio) { + dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); + err = -ENODEV; + goto out_err; + } + + state->bus = of_mdio_find_bus(mdio); + if (!state->bus) { + dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); + err = -ENODEV; + goto out_err; + } + + state->is_indirect = of_property_read_bool(np, "is-indirect"); + + if (state->is_indirect) { + if (of_property_read_u32(np, "reg", &val)) { + dev_err(&pdev->dev, "Switch address not specified\n"); + err = -ENODEV; + goto out_err; + } + + state->base_addr = val; + } else { + state->base_addr = MV_BASE; + } + + state->model = r16(state->bus, state->is_indirect, state->base_addr, + MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + + switch(state->model) { + case MV_IDENT_VALUE_6171: + model_str = MV_IDENT_STR_6171; + break; + case MV_IDENT_VALUE_6172: + model_str = MV_IDENT_STR_6172; + break; + case MV_IDENT_VALUE_6176: + model_str = MV_IDENT_STR_6176; + break; + case MV_IDENT_VALUE_6352: + model_str = MV_IDENT_STR_6352; + break; + default: + dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n", + state->base_addr); + err = -ENODEV; + goto out_err; + } + + platform_set_drvdata(pdev, state); + dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str, + state->bus->id, state->base_addr); + + dev_info(&pdev->dev, "Using %sdirect addressing\n", + (state->is_indirect ? "in" : "")); + + if (of_property_read_u32(np, "cpu-port-0", &val)) { + dev_err(&pdev->dev, "CPU port not set\n"); + err = -ENODEV; + goto out_err; + } + + state->cpu_port0 = val; + + if (!of_property_read_u32(np, "cpu-port-1", &val)) + state->cpu_port1 = val; + else + state->cpu_port1 = -1; + + state->dev.vlans = MV_VLANS; + state->dev.cpu_port = state->cpu_port0; + state->dev.ports = MV_PORTS; + state->dev.name = model_str; + state->dev.ops = &mvsw61xx_ops; + state->dev.alias = dev_name(&pdev->dev); + + _mvsw61xx_reset(&state->dev, true); + + err = register_switch(&state->dev, NULL); + if (err < 0) + goto out_err; + + state->registered = true; + + return 0; +out_err: + kfree(state); + return err; +} + +static int +mvsw61xx_remove(struct platform_device *pdev) +{ + struct mvsw61xx_state *state = platform_get_drvdata(pdev); + + if (state->registered) + unregister_switch(&state->dev); + + kfree(state); + + return 0; +} + +static const struct of_device_id mvsw61xx_match[] = { + { .compatible = "marvell,88e6171" }, + { .compatible = "marvell,88e6172" }, + { .compatible = "marvell,88e6176" }, + { .compatible = "marvell,88e6352" }, + { } +}; +MODULE_DEVICE_TABLE(of, mvsw61xx_match); + +static struct platform_driver mvsw61xx_driver = { + .probe = mvsw61xx_probe, + .remove = mvsw61xx_remove, + .driver = { + .name = "mvsw61xx", + .of_match_table = of_match_ptr(mvsw61xx_match), + .owner = THIS_MODULE, + }, +}; + +static int __init mvsw61xx_module_init(void) +{ + return platform_driver_register(&mvsw61xx_driver); +} +late_initcall(mvsw61xx_module_init); + +static void __exit mvsw61xx_module_exit(void) +{ + platform_driver_unregister(&mvsw61xx_driver); +} +module_exit(mvsw61xx_module_exit); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.h b/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.h new file mode 100644 index 0000000000..a07b09cd14 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/mvsw61xx.h @@ -0,0 +1,292 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __MVSW61XX_H +#define __MVSW61XX_H + +#define MV_PORTS 7 +#define MV_PORTS_MASK ((1 << MV_PORTS) - 1) + +#define MV_BASE 0x10 + +#define MV_SWITCHPORT_BASE 0x10 +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xb) + +#define MV_VLANS 64 + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_PHYCTL = 0x01, + MV_PORT_JAMCTL = 0x02, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_CONTROL1 = 0x05, + MV_PORT_VLANMAP = 0x06, + MV_PORT_VLANID = 0x07, + MV_PORT_CONTROL2 = 0x08, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RX_DISCARD_LOW = 0x10, + MV_PORT_RX_DISCARD_HIGH = 0x11, + MV_PORT_IN_FILTERED = 0x12, + MV_PORT_OUT_ACCEPTED = 0x13, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORT_STATUS_FDX = (1 << 10), + MV_PORT_STATUS_LINK = (1 << 11), +}; + +enum { + MV_PORT_STATUS_CMODE_100BASE_X = 0x8, + MV_PORT_STATUS_CMODE_1000BASE_X = 0x9, + MV_PORT_STATUS_CMODE_SGMII = 0xa, +}; + +#define MV_PORT_STATUS_CMODE_MASK 0xf + +enum { + MV_PORT_STATUS_SPEED_10 = 0x00, + MV_PORT_STATUS_SPEED_100 = 0x01, + MV_PORT_STATUS_SPEED_1000 = 0x02, +}; +#define MV_PORT_STATUS_SPEED_SHIFT 8 +#define MV_PORT_STATUS_SPEED_MASK (3 << 8) + +enum { + MV_PORTCTRL_DISABLED = (0 << 0), + MV_PORTCTRL_BLOCKING = (1 << 0), + MV_PORTCTRL_LEARNING = (2 << 0), + MV_PORTCTRL_FORWARDING = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), + MV_PORTCTRL_EGRESS = (1 << 12), +}; + +#define MV_PHYCTL_FC_MASK (3 << 6) + +enum { + MV_PHYCTL_FC_ENABLE = (3 << 6), + MV_PHYCTL_FC_DISABLE = (1 << 6), +}; + +enum { + MV_8021Q_EGRESS_UNMODIFIED = 0x00, + MV_8021Q_EGRESS_UNTAGGED = 0x01, + MV_8021Q_EGRESS_TAGGED = 0x02, + MV_8021Q_EGRESS_ADDTAG = 0x03, +}; + +#define MV_8021Q_MODE_SHIFT 10 +#define MV_8021Q_MODE_MASK (0x3 << MV_8021Q_MODE_SHIFT) + +enum { + MV_8021Q_MODE_DISABLE = 0x00, + MV_8021Q_MODE_FALLBACK = 0x01, + MV_8021Q_MODE_CHECK = 0x02, + MV_8021Q_MODE_SECURE = 0x03, +}; + +enum { + MV_8021Q_VLAN_ONLY = (1 << 15), +}; + +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_ATU_FID0 = 0x01, + MV_SWITCH_ATU_FID1 = 0x02, + MV_SWITCH_ATU_SID = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, + MV_SWITCH_GLOBAL = 0x1b, + MV_SWITCH_GLOBAL2 = 0x1c, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), + MV_SWITCHCTL_PHYIE = (1 << 1), + MV_SWITCHCTL_ATUDONE = (1 << 2), + MV_SWITCHCTL_ATUIE = (1 << 3), + MV_SWITCHCTL_CTRMODE = (1 << 8), + MV_SWITCHCTL_RELOAD = (1 << 9), + MV_SWITCHCTL_MSIZE = (1 << 10), + MV_SWITCHCTL_DROP = (1 << 13), +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU = (6 << 12), + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +enum { + MV_GLOBAL_STATUS = 0x00, + MV_GLOBAL_ATU_FID = 0x01, + MV_GLOBAL_VTU_FID = 0x02, + MV_GLOBAL_VTU_SID = 0x03, + MV_GLOBAL_CONTROL = 0x04, + MV_GLOBAL_VTU_OP = 0x05, + MV_GLOBAL_VTU_VID = 0x06, + MV_GLOBAL_VTU_DATA1 = 0x07, + MV_GLOBAL_VTU_DATA2 = 0x08, + MV_GLOBAL_VTU_DATA3 = 0x09, + MV_GLOBAL_CONTROL2 = 0x1c, +}; +#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type + +enum { + MV_GLOBAL2_SMI_OP = 0x18, + MV_GLOBAL2_SMI_DATA = 0x19, + MV_GLOBAL2_SDET_POLARITY = 0x1d, +}; +#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type + +enum { + MV_VTU_VID_VALID = (1 << 12), +}; + +enum { + MV_VTUOP_PURGE = (1 << 12), + MV_VTUOP_LOAD = (3 << 12), + MV_VTUOP_INPROGRESS = (1 << 15), + MV_VTUOP_STULOAD = (5 << 12), + MV_VTUOP_VTU_GET_NEXT = (4 << 12), + MV_VTUOP_STU_GET_NEXT = (6 << 12), + MV_VTUOP_GET_VIOLATION = (7 << 12), +}; + +enum { + MV_CONTROL_RESET = (1 << 15), + MV_CONTROL_PPU_ENABLE = (1 << 14), +}; + +enum { + MV_VTUCTL_EGRESS_UNMODIFIED = (0 << 0), + MV_VTUCTL_EGRESS_UNTAGGED = (1 << 0), + MV_VTUCTL_EGRESS_TAGGED = (2 << 0), + MV_VTUCTL_DISCARD = (3 << 0), +}; + +enum { + MV_STUCTL_STATE_DISABLED = (0 << 0), + MV_STUCTL_STATE_BLOCKING = (1 << 0), + MV_STUCTL_STATE_LEARNING = (2 << 0), + MV_STUCTL_STATE_FORWARDING = (3 << 0), +}; + +enum { + MV_INDIRECT_REG_CMD = 0, + MV_INDIRECT_REG_DATA = 1, +}; + +enum { + MV_INDIRECT_INPROGRESS = 0x8000, + MV_INDIRECT_WRITE = 0x9400, + MV_INDIRECT_READ = 0x9800, +}; +#define MV_INDIRECT_ADDR_S 5 + +#define MV_IDENT_MASK 0xfff0 + +#define MV_IDENT_VALUE_6171 0x1710 +#define MV_IDENT_STR_6171 "MV88E6171" + +#define MV_IDENT_VALUE_6172 0x1720 +#define MV_IDENT_STR_6172 "MV88E6172" + +#define MV_IDENT_VALUE_6176 0x1760 +#define MV_IDENT_STR_6176 "MV88E6176" + +#define MV_IDENT_VALUE_6352 0x3520 +#define MV_IDENT_STR_6352 "MV88E6352" + +#define MV_PVID_MASK 0x0fff + +#define MV_FDB_HI_MASK 0x00ff +#define MV_FDB_LO_MASK 0xf000 +#define MV_FDB_HI_SHIFT 4 +#define MV_FDB_LO_SHIFT 12 + +/* Marvell Specific PHY register */ +#define MII_MV_SPEC_CTRL 16 +enum { + MV_SPEC_MDI_CROSS_AUTO = (0x6 << 4), + MV_SPEC_ENERGY_DETECT = (0x3 << 8), + MV_SPEC_DOWNSHIFT_COUNTER = (0x3 << 12), +}; + +#define MII_MV_PAGE 22 + +#define MV_REG_FIBER_SERDES 0xf +#define MV_PAGE_FIBER_SERDES 0x1 + +struct mvsw61xx_state { + struct switch_dev dev; + struct mii_bus *bus; + int base_addr; + u16 model; + + bool registered; + bool is_indirect; + + int cpu_port0; + int cpu_port1; + + int vlan_enabled; + struct port_state { + u16 fdb; + u16 pvid; + u16 mask; + u8 qmode; + } ports[MV_PORTS]; + + struct vlan_state { + bool port_based; + + u16 mask; + u16 vid; + u32 port_mode; + u32 port_sstate; + } vlans[MV_VLANS]; + + char buf[128]; +}; + +#define get_state(_dev) container_of((_dev), struct mvsw61xx_state, dev) + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.c b/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.c new file mode 100644 index 0000000000..043978feb9 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.c @@ -0,0 +1,444 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "mvswitch.h" + +/* Undefine this to use trailer mode instead. + * I don't know if header mode works with all chips */ +#define HEADER_MODE 1 + +MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +#define MVSWITCH_MAGIC 0x88E6060 + +struct mvswitch_priv { + netdev_features_t orig_features; + u8 vlans[16]; +}; + +#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *phydev, int addr, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, addr, reg); +} + +static inline void +w16(struct phy_device *phydev, int addr, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, addr, reg, val); +} + + +static struct sk_buff * +mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + char *buf = NULL; + u16 vid; + + priv = dev->phy_ptr; + if (unlikely(!priv)) + goto error; + + if (unlikely(skb->len < 16)) + goto error; + +#ifdef HEADER_MODE + if (__vlan_hwaccel_get_tag(skb, &vid)) + goto error; + + if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { + if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) + goto error_expand; + if (skb->len < 62) + skb->len = 62; + } + buf = skb_push(skb, MV_HEADER_SIZE); +#else + if (__vlan_get_tag(skb, &vid)) + goto error; + + if (unlikely((vid > 15 || !priv->vlans[vid]))) + goto error; + + if (skb->len <= 64) { + if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) + goto error_expand; + + buf = skb->data + 64; + skb->len = 64 + MV_TRAILER_SIZE; + } else { + if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { + if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) + goto error_expand; + } + buf = skb_put(skb, 4); + } + + /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ + memmove(skb->data + 4, skb->data, 12); + skb->data += 4; + skb->len -= 4; + skb->mac_header += 4; +#endif + + if (!buf) + goto error; + + +#ifdef HEADER_MODE + /* prepend the tag */ + *((__be16 *) buf) = cpu_to_be16( + ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | + ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) + ); +#else + /* append the tag */ + *((__be32 *) buf) = cpu_to_be32(( + (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | + ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) + )); +#endif + + return skb; + +error_expand: + if (net_ratelimit()) + printk("%s: failed to expand/update skb for the switch\n", dev->name); + +error: + /* any errors? drop the packet! */ + dev_kfree_skb_any(skb); + return NULL; +} + +static void +mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + unsigned char *buf; + int vlan = -1; + int i; + + priv = dev->phy_ptr; + if (WARN_ON_ONCE(!priv)) + return; + +#ifdef HEADER_MODE + buf = skb->data; + skb_pull(skb, MV_HEADER_SIZE); +#else + buf = skb->data + skb->len - MV_TRAILER_SIZE; + if (buf[0] != 0x80) + return; +#endif + + /* look for the vlan matching the incoming port */ + for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { + if ((1 << buf[1]) & priv->vlans[i]) + vlan = i; + } + + if (vlan == -1) + return; + + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); +} + + +static int +mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = r16(pdev, addr, reg) & mask; + if (r == val) + return 0; + } while(--i > 0); + return -ETIMEDOUT; +} + +static int +mvswitch_config_init(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + u8 vlmap = 0; + int i; + + if (!dev) + return -EINVAL; + + printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + dev->phy_ptr = priv; + pdev->irq = PHY_POLL; +#ifdef HEADER_MODE + dev->flags |= IFF_PROMISC; +#endif + + /* initialize default vlans */ + for (i = 0; i < MV_PORTS; i++) + priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); + + /* before entering reset, disable all ports */ + for (i = 0; i < MV_PORTS; i++) + w16(pdev, MV_PORTREG(CONTROL, i), 0x00); + + msleep(2); /* wait for the status change to settle in */ + + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); + + i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); + if (i < 0) { + printk("%s: Timeout waiting for the switch to reset.\n", dev->name); + return i; + } + + /* set the ATU flags */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), + MV_ATUCTL_NO_LEARN | + MV_ATUCTL_ATU_1K | + MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ + ); + + /* initialize the cpu port */ + w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), +#ifdef HEADER_MODE + MV_PORTCTRL_HEADER | +#else + MV_PORTCTRL_RXTR | + MV_PORTCTRL_TXTR | +#endif + MV_PORTCTRL_ENABLED + ); + /* wait for the phy change to settle in */ + msleep(2); + for (i = 0; i < MV_PORTS; i++) { + u8 pvid = 0; + int j; + + vlmap = 0; + + /* look for the matching vlan */ + for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { + if (priv->vlans[j] & (1 << i)) { + vlmap = priv->vlans[j]; + pvid = j; + } + } + /* leave port unconfigured if it's not part of a vlan */ + if (!vlmap) + continue; + + /* add the cpu port to the allowed destinations list */ + vlmap |= (1 << MV_CPUPORT); + + /* take port out of its own vlan destination map */ + vlmap &= ~(1 << i); + + /* apply vlan settings */ + w16(pdev, MV_PORTREG(VLANMAP, i), + MV_PORTVLAN_PORTS(vlmap) | + MV_PORTVLAN_ID(i) + ); + + /* re-enable port */ + w16(pdev, MV_PORTREG(CONTROL, i), + MV_PORTCTRL_ENABLED + ); + } + + w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), + MV_PORTVLAN_ID(MV_CPUPORT) + ); + + /* set the port association vector */ + for (i = 0; i <= MV_PORTS; i++) { + w16(pdev, MV_PORTREG(ASSOC, i), + MV_PORTASSOC_PORTS(1 << i) + ); + } + + /* init switch control */ + w16(pdev, MV_SWITCHREG(CTRL), + MV_SWITCHCTL_MSIZE | + MV_SWITCHCTL_DROP + ); + + dev->eth_mangle_rx = mvswitch_mangle_rx; + dev->eth_mangle_tx = mvswitch_mangle_tx; + priv->orig_features = dev->features; + +#ifdef HEADER_MODE + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; +#else + dev->features |= NETIF_F_HW_VLAN_CTAG_RX; +#endif + + return 0; +} + +static int +mvswitch_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + + /* XXX ugly workaround: we can't force the switch + * to gracefully handle hosts moving from one port to another, + * so we have to regularly clear the ATU database */ + + /* wait for the ATU to become available */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + /* flush the ATU */ + w16(pdev, MV_SWITCHREG(ATU_OP), + MV_ATUOP_INPROGRESS | + MV_ATUOP_FLUSH_ALL + ); + + /* wait for operation to complete */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + return 0; +} + +static int +mvswitch_aneg_done(struct phy_device *phydev) +{ + return 1; /* Return any positive value */ +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static void +mvswitch_detach(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + dev->features = priv->orig_features; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + + kfree(priv); +} + +static int +mvswitch_probe(struct phy_device *pdev) +{ + struct mvswitch_priv *priv; + + priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + pdev->priv = priv; + + return 0; +} + +static int +mvswitch_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + if (dev->mdio.addr != 0x10) + return 0; + + reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) + return 0; + + dev->phy_id = MVSWITCH_MAGIC; + return 0; +} + + +static struct phy_driver mvswitch_driver = { + .name = "Marvell 88E6060", + .phy_id = MVSWITCH_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &mvswitch_probe, + .remove = &mvswitch_remove, + .detach = &mvswitch_detach, + .config_init = &mvswitch_config_init, + .config_aneg = &mvswitch_config_aneg, + .aneg_done = &mvswitch_aneg_done, + .read_status = &mvswitch_read_status, +}; + +static int __init +mvswitch_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); + return phy_driver_register(&mvswitch_driver, THIS_MODULE); +} + +static void __exit +mvswitch_exit(void) +{ + phy_driver_unregister(&mvswitch_driver); +} + +module_init(mvswitch_init); +module_exit(mvswitch_exit); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.h b/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.h new file mode 100644 index 0000000000..ab2a1a126e --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/mvswitch.h @@ -0,0 +1,145 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __MVSWITCH_H +#define __MVSWITCH_H + +#define MV_HEADER_SIZE 2 +#define MV_HEADER_PORTS_M 0x001f +#define MV_HEADER_PORTS_S 0 +#define MV_HEADER_VLAN_M 0xf000 +#define MV_HEADER_VLAN_S 12 + +#define MV_TRAILER_SIZE 4 +#define MV_TRAILER_PORTS_M 0x1f +#define MV_TRAILER_PORTS_S 16 +#define MV_TRAILER_FLAGS_S 24 +#define MV_TRAILER_OVERRIDE 0x80 + + +#define MV_PORTS 5 +#define MV_WANPORT 4 +#define MV_CPUPORT 5 + +#define MV_BASE 0x10 + +#define MV_PHYPORT_BASE (MV_BASE + 0x0) +#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) +#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xf) + +enum { + MV_PHY_CONTROL = 0x00, + MV_PHY_STATUS = 0x01, + MV_PHY_IDENT0 = 0x02, + MV_PHY_IDENT1 = 0x03, + MV_PHY_ANEG = 0x04, + MV_PHY_LINK_ABILITY = 0x05, + MV_PHY_ANEG_EXPAND = 0x06, + MV_PHY_XMIT_NEXTP = 0x07, + MV_PHY_LINK_NEXTP = 0x08, + MV_PHY_CONTROL1 = 0x10, + MV_PHY_STATUS1 = 0x11, + MV_PHY_INTR_EN = 0x12, + MV_PHY_INTR_STATUS = 0x13, + MV_PHY_INTR_PORT = 0x14, + MV_PHY_RECV_COUNTER = 0x16, + MV_PHY_LED_PARALLEL = 0x16, + MV_PHY_LED_STREAM = 0x17, + MV_PHY_LED_CTRL = 0x18, + MV_PHY_LED_OVERRIDE = 0x19, + MV_PHY_VCT_CTRL = 0x1a, + MV_PHY_VCT_STATUS = 0x1b, + MV_PHY_CONTROL2 = 0x1e +}; +#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_VLANMAP = 0x06, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ + MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ + MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ + MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ + MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ +}; + +#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) +#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) + +#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ + MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ + MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ + MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ + MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ + MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ + MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ + MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU= (6 << 12), + + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +#define MV_IDENT_MASK 0xfff0 +#define MV_IDENT_VALUE 0x0600 + +#endif diff --git a/target/linux/generic/files-4.14/drivers/net/phy/psb6970.c b/target/linux/generic/files-4.14/drivers/net/phy/psb6970.c new file mode 100644 index 0000000000..c1a381c052 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/psb6970.c @@ -0,0 +1,441 @@ +/* + * Lantiq PSB6970 (Tantos) Switch driver + * + * Copyright (c) 2009,2010 Team Embedded. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * The switch programming done in this driver follows the + * "Ethernet Traffic Separation using VLAN" Application Note as + * published by Lantiq. + */ + +#include +#include +#include +#include + +#define PSB6970_MAX_VLANS 16 +#define PSB6970_NUM_PORTS 7 +#define PSB6970_DEFAULT_PORT_CPU 6 +#define PSB6970_IS_CPU_PORT(x) ((x) > 4) + +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +/* --- Identification --- */ +#define PSB6970_CI0 0x0100 +#define PSB6970_CI0_MASK 0x000f +#define PSB6970_CI1 0x0101 +#define PSB6970_CI1_VAL 0x2599 +#define PSB6970_CI1_MASK 0xffff + +/* --- VLAN filter table --- */ +#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ +#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ + +#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ +#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ + +/* --- Port registers --- */ +#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ +#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ + +#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ +#define PSB6970_PBVM_VMCE (1 << 8) +#define PSB6970_PBVM_AOVTP (1 << 9) +#define PSB6970_PBVM_VSD (1 << 10) +#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ +#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ + +#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ + +struct psb6970_priv { + struct switch_dev dev; + struct phy_device *phy; + u16 (*read) (struct phy_device* phydev, int reg); + void (*write) (struct phy_device* phydev, int reg, u16 val); + struct mutex reg_mutex; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[PSB6970_MAX_VLANS]; + u8 vlan_table[PSB6970_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[PSB6970_NUM_PORTS]; +}; + +#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) + +static u16 psb6970_mii_read(struct phy_device *phydev, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan; + return 0; +} + +static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + + /* make sure no invalid PVIDs get set */ + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static struct switch_attr psb6970_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = psb6970_set_vlan, + .get = psb6970_get_vlan, + .max = 1}, +}; + +static struct switch_attr psb6970_port[] = { +}; + +static struct switch_attr psb6970_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = psb6970_set_vid, + .get = psb6970_get_vid, + .max = 4094, + }, +}; + +static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + priv->vlan_tagged |= (1 << p->id); + else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int psb6970_hw_apply(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i, j; + + mutex_lock(&priv->reg_mutex); + + if (priv->vlan) { + /* into the vlan translation unit */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (vp) { + priv->write(priv->phy, PSB6970_VFxL(j), + PSB6970_VFxL_VV | priv->vlan_id[j]); + priv->write(priv->phy, PSB6970_VFxH(j), + ((vp & priv-> + vlan_tagged) << + PSB6970_VFxH_TM_SHIFT) | vp); + } else /* clear VLAN Valid flag for unused vlans */ + priv->write(priv->phy, PSB6970_VFxL(j), 0); + + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; + + if (priv->vlan) { + ec = PSB6970_EC_IFNTE; + dvid = priv->vlan_id[priv->pvid[i]]; + pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; + + if ((i << 1) & priv->vlan_tagged) + pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; + } + + priv->write(priv->phy, PSB6970_PBVM(i), pbvm); + + if (!PSB6970_IS_CPU_PORT(i)) { + priv->write(priv->phy, PSB6970_EC(i), ec); + priv->write(priv->phy, PSB6970_DVID(i), dvid); + } + } + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int psb6970_reset_switch(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i; + + mutex_lock(&priv->reg_mutex); + + memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - + offsetof(struct psb6970_priv, vlan)); + + for (i = 0; i < PSB6970_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + mutex_unlock(&priv->reg_mutex); + + return psb6970_hw_apply(dev); +} + +static const struct switch_dev_ops psb6970_ops = { + .attr_global = { + .attr = psb6970_globals, + .n_attr = ARRAY_SIZE(psb6970_globals), + }, + .attr_port = { + .attr = psb6970_port, + .n_attr = ARRAY_SIZE(psb6970_port), + }, + .attr_vlan = { + .attr = psb6970_vlan, + .n_attr = ARRAY_SIZE(psb6970_vlan), + }, + .get_port_pvid = psb6970_get_pvid, + .set_port_pvid = psb6970_set_pvid, + .get_vlan_ports = psb6970_get_ports, + .set_vlan_ports = psb6970_set_ports, + .apply_config = psb6970_hw_apply, + .reset_switch = psb6970_reset_switch, +}; + +static int psb6970_config_init(struct phy_device *pdev) +{ + struct psb6970_priv *priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->phy = pdev; + + if (pdev->mdio.addr == 0) + printk(KERN_INFO "%s: psb6970 switch driver attached.\n", + pdev->attached_dev->name); + + if (pdev->mdio.addr != 0) { + kfree(priv); + return 0; + } + + pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; + + mutex_init(&priv->reg_mutex); + priv->read = psb6970_mii_read; + priv->write = psb6970_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; + swdev->ops = &psb6970_ops; + + swdev->name = "Lantiq PSB6970"; + swdev->vlans = PSB6970_MAX_VLANS; + swdev->ports = PSB6970_NUM_PORTS; + + if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { + kfree(priv); + goto done; + } + + ret = psb6970_reset_switch(&priv->dev); + if (ret) { + kfree(priv); + goto done; + } + + dev->phy_ptr = priv; + +done: + return ret; +} + +static int psb6970_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int psb6970_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int psb6970_probe(struct phy_device *pdev) +{ + return 0; +} + +static void psb6970_remove(struct phy_device *pdev) +{ + struct psb6970_priv *priv = pdev->priv; + + if (!priv) + return; + + if (pdev->mdio.addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; + if (reg != PSB6970_CI1_VAL) + return 0; + + dev->phy_id = (reg << 16); + dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; + + return 0; +} + +static struct phy_driver psb6970_driver = { + .name = "Lantiq PSB6970", + .phy_id = PSB6970_CI1_VAL << 16, + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = psb6970_probe, + .remove = psb6970_remove, + .config_init = &psb6970_config_init, + .config_aneg = &psb6970_config_aneg, + .read_status = &psb6970_read_status, +}; + +int __init psb6970_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); + return phy_driver_register(&psb6970_driver, THIS_MODULE); +} + +module_init(psb6970_init); + +void __exit psb6970_exit(void) +{ + phy_driver_unregister(&psb6970_driver); +} + +module_exit(psb6970_exit); + +MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); +MODULE_AUTHOR("Ithamar R. Adema "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8306.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8306.c new file mode 100644 index 0000000000..6d09c1063c --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8306.c @@ -0,0 +1,1066 @@ +/* + * rtl8306.c: RTL8306S switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG 1 + +/* Global (PHY0) */ +#define RTL8306_REG_PAGE 16 +#define RTL8306_REG_PAGE_LO (1 << 15) +#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ + +#define RTL8306_NUM_VLANS 16 +#define RTL8306_NUM_PORTS 6 +#define RTL8306_PORT_CPU 5 +#define RTL8306_NUM_PAGES 4 +#define RTL8306_NUM_REGS 32 + +#define RTL_NAME_S "RTL8306S" +#define RTL_NAME_SD "RTL8306SD" +#define RTL_NAME_SDM "RTL8306SDM" +#define RTL_NAME_UNKNOWN "RTL8306(unknown)" + +#define RTL8306_MAGIC 0x8306 + +static LIST_HEAD(phydevs); + +struct rtl_priv { + struct list_head list; + struct switch_dev dev; + int page; + int type; + int do_cpu; + struct mii_bus *bus; + char hwname[sizeof(RTL_NAME_UNKNOWN)]; + bool fixup; +}; + +struct rtl_phyregs { + int nway; + int speed; + int duplex; +}; + +#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) + +enum { + RTL_TYPE_S, + RTL_TYPE_SD, + RTL_TYPE_SDM, +}; + +struct rtl_reg { + int page; + int phy; + int reg; + int bits; + int shift; + int inverted; +}; + +#define RTL_VLAN_REGOFS(name) \ + (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) + +#define RTL_PORT_REGOFS(name) \ + (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) + +#define RTL_PORT_REG(id, reg) \ + (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) + +#define RTL_VLAN_REG(id, reg) \ + (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) + +#define RTL_GLOBAL_REGATTR(reg) \ + .id = RTL_REG_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = 0, \ + .set = rtl_attr_set_int, \ + .get = rtl_attr_get_int + +#define RTL_PORT_REGATTR(reg) \ + .id = RTL_REG_PORT0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_PORT_REGOFS(reg), \ + .set = rtl_attr_set_port_int, \ + .get = rtl_attr_get_port_int + +#define RTL_VLAN_REGATTR(reg) \ + .id = RTL_REG_VLAN0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_VLAN_REGOFS(reg), \ + .set = rtl_attr_set_vlan_int, \ + .get = rtl_attr_get_vlan_int + +enum rtl_regidx { + RTL_REG_CHIPID, + RTL_REG_CHIPVER, + RTL_REG_CHIPTYPE, + RTL_REG_CPUPORT, + + RTL_REG_EN_CPUPORT, + RTL_REG_EN_TAG_OUT, + RTL_REG_EN_TAG_CLR, + RTL_REG_EN_TAG_IN, + RTL_REG_TRAP_CPU, + RTL_REG_CPU_LINKUP, + RTL_REG_TRUNK_PORTSEL, + RTL_REG_EN_TRUNK, + RTL_REG_RESET, + + RTL_REG_VLAN_ENABLE, + RTL_REG_VLAN_FILTER, + RTL_REG_VLAN_TAG_ONLY, + RTL_REG_VLAN_TAG_AWARE, +#define RTL_VLAN_ENUM(id) \ + RTL_REG_VLAN##id##_VID, \ + RTL_REG_VLAN##id##_PORTMASK + RTL_VLAN_ENUM(0), + RTL_VLAN_ENUM(1), + RTL_VLAN_ENUM(2), + RTL_VLAN_ENUM(3), + RTL_VLAN_ENUM(4), + RTL_VLAN_ENUM(5), + RTL_VLAN_ENUM(6), + RTL_VLAN_ENUM(7), + RTL_VLAN_ENUM(8), + RTL_VLAN_ENUM(9), + RTL_VLAN_ENUM(10), + RTL_VLAN_ENUM(11), + RTL_VLAN_ENUM(12), + RTL_VLAN_ENUM(13), + RTL_VLAN_ENUM(14), + RTL_VLAN_ENUM(15), +#define RTL_PORT_ENUM(id) \ + RTL_REG_PORT##id##_PVID, \ + RTL_REG_PORT##id##_NULL_VID_REPLACE, \ + RTL_REG_PORT##id##_NON_PVID_DISCARD, \ + RTL_REG_PORT##id##_VID_INSERT, \ + RTL_REG_PORT##id##_TAG_INSERT, \ + RTL_REG_PORT##id##_LINK, \ + RTL_REG_PORT##id##_SPEED, \ + RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ + RTL_REG_PORT##id##_DUPLEX, \ + RTL_REG_PORT##id##_RXEN, \ + RTL_REG_PORT##id##_TXEN + RTL_PORT_ENUM(0), + RTL_PORT_ENUM(1), + RTL_PORT_ENUM(2), + RTL_PORT_ENUM(3), + RTL_PORT_ENUM(4), + RTL_PORT_ENUM(5), +}; + +static const struct rtl_reg rtl_regs[] = { + [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, + [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, + [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, + + /* CPU port number */ + [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, + /* Enable CPU port function */ + [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, + /* Enable CPU port tag insertion */ + [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, + /* Enable CPU port tag removal */ + [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, + /* Enable CPU port tag checking */ + [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, + [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, + [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, + [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, + + [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, + [RTL_REG_CPU_LINKUP] = { 0, 6, 22, 1, 15, 0 }, + + [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, + [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, + [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, + [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, + +#define RTL_VLAN_REGS(id, phy, page, regofs) \ + [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ + [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } + RTL_VLAN_REGS( 0, 0, 0, 0), + RTL_VLAN_REGS( 1, 1, 0, 0), + RTL_VLAN_REGS( 2, 2, 0, 0), + RTL_VLAN_REGS( 3, 3, 0, 0), + RTL_VLAN_REGS( 4, 4, 0, 0), + RTL_VLAN_REGS( 5, 0, 1, 2), + RTL_VLAN_REGS( 6, 1, 1, 2), + RTL_VLAN_REGS( 7, 2, 1, 2), + RTL_VLAN_REGS( 8, 3, 1, 2), + RTL_VLAN_REGS( 9, 4, 1, 2), + RTL_VLAN_REGS(10, 0, 1, 4), + RTL_VLAN_REGS(11, 1, 1, 4), + RTL_VLAN_REGS(12, 2, 1, 4), + RTL_VLAN_REGS(13, 3, 1, 4), + RTL_VLAN_REGS(14, 4, 1, 4), + RTL_VLAN_REGS(15, 0, 1, 6), + +#define REG_PORT_SETTING(port, phy) \ + [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ + [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ + [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ + [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ + [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ + [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ + [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } + + REG_PORT_SETTING(0, 0), + REG_PORT_SETTING(1, 1), + REG_PORT_SETTING(2, 2), + REG_PORT_SETTING(3, 3), + REG_PORT_SETTING(4, 4), + REG_PORT_SETTING(5, 6), + +#define REG_PORT_PVID(phy, page, regofs) \ + { page, phy, 24 + regofs, 4, 12, 0 } + [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), + [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), + [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), + [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), + [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), + [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), +}; + + +static inline void +rtl_set_page(struct rtl_priv *priv, unsigned int page) +{ + struct mii_bus *bus = priv->bus; + u16 pgsel; + + if (priv->fixup) + return; + + if (priv->page == page) + return; + + BUG_ON(page > RTL8306_NUM_PAGES); + pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); + pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); + if (page & (1 << 0)) + pgsel |= RTL8306_REG_PAGE_LO; + if (!(page & (1 << 1))) /* bit is inverted */ + pgsel |= RTL8306_REG_PAGE_HI; + bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); +} + +static inline int +rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + bus->write(bus, phy, reg, val); + bus->read(bus, phy, reg); /* flush */ + return 0; +} + +static inline int +rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + return bus->read(bus, phy, reg); +} + +static inline u16 +rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + u16 r; + + rtl_set_page(priv, page); + r = bus->read(bus, phy, reg); + r &= ~mask; + r |= val; + bus->write(bus, phy, reg, r); + return bus->read(bus, phy, reg); /* flush */ +} + + +static inline int +rtl_get(struct switch_dev *dev, enum rtl_regidx s) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 val; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + if (r->bits == 0) /* unimplemented */ + return 0; + + val = rtl_r16(dev, r->page, r->phy, r->reg); + + if (r->shift > 0) + val >>= r->shift; + + if (r->inverted) + val = ~val; + + val &= (1 << r->bits) - 1; + + return val; +} + +static int +rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 mask = 0xffff; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + + if (r->bits == 0) /* unimplemented */ + return 0; + + if (r->shift > 0) + val <<= r->shift; + + if (r->inverted) + val = ~val; + + if (r->bits != 16) { + mask = (1 << r->bits) - 1; + mask <<= r->shift; + } + val &= mask; + return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); +} + +static void +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); +} + +static void +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); +} + +static void +rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) +{ + rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); + rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); +} + +static int +rtl_hw_apply(struct switch_dev *dev) +{ + int i; + int trunk_en, trunk_psel; + struct rtl_phyregs port5; + + rtl_phy_save(dev, 5, &port5); + + /* disable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 0); + } + + /* save trunking status */ + trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); + trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); + + /* trunk port 3 and 4 + * XXX: Big WTF, but RealTek seems to do it */ + rtl_set(dev, RTL_REG_EN_TRUNK, 1); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); + + /* execute the software reset */ + rtl_set(dev, RTL_REG_RESET, 1); + + /* wait for the reset to complete, + * but don't wait for too long */ + for (i = 0; i < 10; i++) { + if (rtl_get(dev, RTL_REG_RESET) == 0) + break; + + msleep(1); + } + + /* enable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 1); + } + + /* restore trunking settings */ + rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); + rtl_phy_restore(dev, 5, &port5); + + rtl_set(dev, RTL_REG_CPU_LINKUP, 1); + + return 0; +} + +static void +rtl_hw_init(struct switch_dev *dev) +{ + struct rtl_priv *priv = to_rtl(dev); + int cpu_mask = 1 << dev->cpu_port; + int i; + + rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); + rtl_set(dev, RTL_REG_VLAN_FILTER, 0); + rtl_set(dev, RTL_REG_EN_TRUNK, 0); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); + + /* initialize cpu port settings */ + if (priv->do_cpu) { + rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); + rtl_set(dev, RTL_REG_EN_CPUPORT, 1); + } else { + rtl_set(dev, RTL_REG_CPUPORT, 7); + rtl_set(dev, RTL_REG_EN_CPUPORT, 0); + } + rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); + rtl_set(dev, RTL_REG_EN_TAG_IN, 0); + rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); + + /* reset all vlans */ + for (i = 0; i < RTL8306_NUM_VLANS; i++) { + rtl_set(dev, RTL_VLAN_REG(i, VID), i); + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); + } + + /* default to port isolation */ + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + unsigned long mask; + + if ((1 << i) == cpu_mask) + mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ + else + mask = cpu_mask | (1 << i); + + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); + rtl_set(dev, RTL_PORT_REG(i, PVID), i); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); + } + rtl_hw_apply(dev); +} + +#ifdef DEBUG +static int +rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + priv->do_cpu = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + val->value.i = priv->do_cpu; + return 0; +} + +static int +rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + dev->cpu_port = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = dev->cpu_port; + return 0; +} +#endif + +static int +rtl_reset(struct switch_dev *dev) +{ + rtl_hw_init(dev); + return 0; +} + +static int +rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; + + if (attr->id >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + if ((attr->max > 0) && (val->value.i > attr->max)) + return -EINVAL; + + /* access to phy register 22 on port 4/5 + * needs phy status save/restore */ + if ((val->port_vlan > 3) && + (rtl_regs[idx].reg == 22) && + (rtl_regs[idx].page == 0)) { + + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, idx, val->value.i); + rtl_phy_restore(dev, val->port_vlan, &port); + } else { + rtl_set(dev, idx, val->value.i); + } + + return 0; +} + +static int +rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + + if (idx >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + val->value.i = rtl_get(dev, idx); + return 0; +} + +static int +rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link) +{ + if (port >= RTL8306_NUM_PORTS) + return -EINVAL; + + /* in case the link changes from down to up, the register is only updated on read */ + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + if (!link->link) + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + + if (!link->link) + return 0; + + link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); + link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + + if (rtl_get(dev, RTL_PORT_REG(port, SPEED))) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int i, mask; + + mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + struct switch_port *port; + + if (!(mask & (1 << i))) + continue; + + port = &val->value.ports[val->len]; + port->id = i; + if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port) + port->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; + int en = val->value.i; + int i; + + rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); + rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); + if (en) + rtl_set(dev, RTL_REG_VLAN_FILTER, en); + + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (i > 3) + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); + if (i > 3) + rtl_phy_restore(dev, val->port_vlan, &port); + } + rtl_set(dev, RTL_REG_VLAN_ENABLE, en); + + return 0; +} + +static int +rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE); + return 0; +} + +static int +rtl_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int mask = 0; + unsigned int oldmask; + int i; + + for(i = 0; i < val->len; i++) + { + struct switch_port *port = &val->value.ports[i]; + bool tagged = false; + + mask |= (1 << port->id); + + if (port->id == dev->cpu_port) + continue; + + if ((i == dev->cpu_port) || + (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + tagged = true; + + /* fix up PVIDs for added ports */ + if (!tagged) + rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); + + rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); + } + + oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); + + /* fix up PVIDs for removed ports, default to last vlan */ + oldmask &= ~mask; + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (!(oldmask & (1 << i))) + continue; + + if (i == dev->cpu_port) + continue; + + if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) + rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); + } + + return 0; +} + +static struct switch_attr rtl_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .max = 1, + .set = rtl_set_vlan, + .get = rtl_get_vlan, + }, + { + RTL_GLOBAL_REGATTR(EN_TRUNK), + .name = "trunk", + .description = "Enable port trunking", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), + .name = "trunk_sel", + .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", + .max = 1, + }, +#ifdef DEBUG + { + RTL_GLOBAL_REGATTR(VLAN_FILTER), + .name = "vlan_filter", + .description = "Filter incoming packets for allowed VLANS", + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "cpuport", + .description = "CPU Port", + .set = rtl_set_cpuport, + .get = rtl_get_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + .type = SWITCH_TYPE_INT, + .name = "use_cpuport", + .description = "CPU Port handling flag", + .set = rtl_set_use_cpuport, + .get = rtl_get_use_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + RTL_GLOBAL_REGATTR(TRAP_CPU), + .name = "trap_cpu", + .description = "VLAN trap to CPU", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), + .name = "vlan_tag_aware", + .description = "Enable VLAN tag awareness", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), + .name = "tag_only", + .description = "Only accept tagged packets", + .max = 1, + }, +#endif +}; +static struct switch_attr rtl_port[] = { + { + RTL_PORT_REGATTR(PVID), + .name = "pvid", + .description = "Port VLAN ID", + .max = RTL8306_NUM_VLANS - 1, + }, +#ifdef DEBUG + { + RTL_PORT_REGATTR(NULL_VID_REPLACE), + .name = "null_vid", + .description = "NULL VID gets replaced by port default vid", + .max = 1, + }, + { + RTL_PORT_REGATTR(NON_PVID_DISCARD), + .name = "non_pvid_discard", + .description = "discard packets with VID != PVID", + .max = 1, + }, + { + RTL_PORT_REGATTR(VID_INSERT), + .name = "vid_insert_remove", + .description = "how should the switch insert and remove vids ?", + .max = 3, + }, + { + RTL_PORT_REGATTR(TAG_INSERT), + .name = "tag_insert", + .description = "tag insertion handling", + .max = 3, + }, +#endif +}; + +static struct switch_attr rtl_vlan[] = { + { + RTL_VLAN_REGATTR(VID), + .name = "vid", + .description = "VLAN ID (1-4095)", + .max = 4095, + }, +}; + +static const struct switch_dev_ops rtl8306_ops = { + .attr_global = { + .attr = rtl_globals, + .n_attr = ARRAY_SIZE(rtl_globals), + }, + .attr_port = { + .attr = rtl_port, + .n_attr = ARRAY_SIZE(rtl_port), + }, + .attr_vlan = { + .attr = rtl_vlan, + .n_attr = ARRAY_SIZE(rtl_vlan), + }, + + .get_vlan_ports = rtl_get_ports, + .set_vlan_ports = rtl_set_ports, + .apply_config = rtl_hw_apply, + .reset_switch = rtl_reset, + .get_port_link = rtl_get_port_link, +}; + +static int +rtl8306_config_init(struct phy_device *pdev) +{ + struct net_device *netdev = pdev->attached_dev; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + struct switch_val val; + unsigned int chipid, chipver, chiptype; + int err; + + /* Only init the switch for the primary PHY */ + if (pdev->mdio.addr != 0) + return 0; + + val.value.i = 1; + priv->dev.cpu_port = RTL8306_PORT_CPU; + priv->dev.ports = RTL8306_NUM_PORTS; + priv->dev.vlans = RTL8306_NUM_VLANS; + priv->dev.ops = &rtl8306_ops; + priv->do_cpu = 0; + priv->page = -1; + priv->bus = pdev->mdio.bus; + + chipid = rtl_get(dev, RTL_REG_CHIPID); + chipver = rtl_get(dev, RTL_REG_CHIPVER); + chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); + switch(chiptype) { + case 0: + case 2: + strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); + priv->type = RTL_TYPE_S; + break; + case 1: + strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SD; + break; + case 3: + strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SDM; + break; + default: + strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); + break; + } + + dev->name = priv->hwname; + rtl_hw_init(dev); + + printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); + + err = register_switch(dev, netdev); + if (err < 0) { + kfree(priv); + return err; + } + + return 0; +} + + +static int +rtl8306_fixup(struct phy_device *pdev) +{ + struct rtl_priv priv; + u16 chipid; + + /* Attach to primary LAN port and WAN port */ + if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4) + return 0; + + memset(&priv, 0, sizeof(priv)); + priv.fixup = true; + priv.page = -1; + priv.bus = pdev->mdio.bus; + chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); + if (chipid == 0x5988) + pdev->phy_id = RTL8306_MAGIC; + + return 0; +} + +static int +rtl8306_probe(struct phy_device *pdev) +{ + struct rtl_priv *priv; + + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->mdio.bus) + goto found; + } + priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = pdev->mdio.bus; + +found: + pdev->priv = priv; + return 0; +} + +static void +rtl8306_remove(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + unregister_switch(&priv->dev); + kfree(priv); +} + +static int +rtl8306_config_aneg(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->mdio.addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + + return 0; +} + +static int +rtl8306_read_status(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->mdio.addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } + + return 0; +} + + +static struct phy_driver rtl8306_driver = { + .name = "Realtek RTL8306S", +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0)) + .flags = PHY_HAS_MAGICANEG, +#endif + .phy_id = RTL8306_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &rtl8306_probe, + .remove = &rtl8306_remove, + .config_init = &rtl8306_config_init, + .config_aneg = &rtl8306_config_aneg, + .read_status = &rtl8306_read_status, +}; + + +static int __init +rtl_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); + return phy_driver_register(&rtl8306_driver, THIS_MODULE); +} + +static void __exit +rtl_exit(void) +{ + phy_driver_unregister(&rtl8306_driver); +} + +module_init(rtl_init); +module_exit(rtl_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 0000000000..c0cb680e23 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,1628 @@ +/* + * Realtek RTL8366 SMI interface driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +#include +#endif + +#include "rtl8366_smi.h" + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 + +#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ +#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = !!gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { + dev_err(smi->parent, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return 0; +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +static int __rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} +/* Read/write via mdiobus */ +#define MDC_MDIO_CTRL0_REG 31 +#define MDC_MDIO_START_REG 29 +#define MDC_MDIO_CTRL1_REG 21 +#define MDC_MDIO_ADDRESS_REG 23 +#define MDC_MDIO_DATA_WRITE_REG 24 +#define MDC_MDIO_DATA_READ_REG 25 + +#define MDC_MDIO_START_OP 0xFFFF +#define MDC_MDIO_ADDR_OP 0x000E +#define MDC_MDIO_READ_OP 0x0001 +#define MDC_MDIO_WRITE_OP 0x0003 +#define MDC_REALTEK_PHY_ADDR 0x0 + +int __rtl8366_mdio_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write read control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_READ_OP); + + /* Write Start command to register 29 */ + mbus->write(smi->ext_mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Read data from register 25 */ + *data = mbus->read(mbus, phy_id, MDC_MDIO_DATA_READ_REG); + + mutex_unlock(&mbus->mdio_lock); + + return 0; +} + +static int __rtl8366_mdio_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data to register 24 */ + mbus->write(mbus, phy_id, MDC_MDIO_DATA_WRITE_REG, data); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_WRITE_OP); + + mutex_unlock(&mbus->mdio_lock); + return 0; +} + +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_read_reg(smi, addr, data); + else + return __rtl8366_smi_read_reg(smi, addr, data); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); + +static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + if (ack) + ret = rtl8366_smi_write_byte(smi, data >> 8); + else + ret = rtl8366_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_write_reg(smi, addr, data); + else + return __rtl8366_smi_write_reg(smi, addr, data, true); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); + +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); + +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) +{ + u32 t; + int err; + + err = rtl8366_smi_read_reg(smi, addr, &t); + if (err) + return err; + + err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); + return err; + +} +EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); + +static int rtl8366_reset(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) { + smi->hw_reset(smi, true); + msleep(RTL8366_SMI_HW_STOP_DELAY); + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + return 0; + } + + return smi->ops->reset_chip(smi); +} + +static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) +{ + int err; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + err = smi->ops->get_mc_index(smi, i, &index); + if (err) + return err; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} + +static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Update the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + err = smi->ops->set_vlan_4k(smi, &vlan4k); + if (err) + return err; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return err; +} + +static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int index; + + err = smi->ops->get_mc_index(smi, port, &index); + if (err) + return err; + + err = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (err) + return err; + + *val = vlanmc.vid; + return 0; +} + +static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, + unsigned vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + err = rtl8366_mc_is_used(smi, i, &used); + if (err) + return err; + + if (!used) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + dev_err(smi->parent, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} + +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + int err; + + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + + if (!enable) { + smi->vlan4k_enabled = 0; + err = smi->ops->enable_vlan4k(smi, enable); + } + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + int err; + + if (enable) { + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + } + + err = smi->ops->enable_vlan4k(smi, enable); + if (err) + return err; + + smi->vlan4k_enabled = enable; + return 0; +} + +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) +{ + int port; + int err; + + for (port = 0; port < smi->num_ports; port++) { + err = smi->ops->enable_port(smi, port, enable); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int i; + + rtl8366_enable_vlan(smi, 0); + rtl8366_enable_vlan4k(smi, 0); + + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +static int rtl8366_init_vlan(struct rtl8366_smi *smi) +{ + int port; + int err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + mask = (1 << smi->num_ports) - 1; + else + mask = (1 << port) | (1 << smi->cpu_port); + + err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (err) + return err; + + err = rtl8366_set_pvid(smi, port, (port + 1)); + if (err) + return err; + } + + return rtl8366_enable_vlan(smi, 1); +} + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); + +static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2s %6s %4s %6s %6s %3s\n", + "id", "vid","prio", "member", "untag", "fid"); + + for (i = 0; i < smi->num_vlan_mc; ++i) { + struct rtl8366_vlan_mc vlanmc; + + smi->ops->get_vlan_mc(smi, i, &vlanmc); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2d %6d %4d 0x%04x 0x%04x %3d\n", + i, vlanmc.vid, vlanmc.priority, + vlanmc.member, vlanmc.untag, vlanmc.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define RTL8366_VLAN4K_PAGE_SIZE 64 +#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) + +static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + int offset; + char *buf = smi->buf; + + if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "invalid page: %u\n", smi->dbg_vlan_4k_page); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4s %6s %6s %3s\n", + "vid", "member", "untag", "fid"); + + offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; + for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { + struct rtl8366_vlan_4k vlan4k; + + smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d 0x%04x 0x%04x %3d\n", + vlan4k.vid, vlan4k.member, + vlan4k.untag, vlan4k.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_pvid(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + char *buf = smi->buf; + int len = 0; + int i; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", + "port", "pvid"); + + for (i = 0; i < smi->num_ports; i++) { + int pvid; + int err; + + err = rtl8366_get_pvid(smi, i, &pvid); + if (err) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d error\n", i); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d %4d\n", i, pvid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_reg(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + u32 t, reg = smi->dbg_reg; + int err, len = 0; + char *buf = smi->buf; + + memset(buf, '\0', sizeof(smi->buf)); + + err = rtl8366_smi_read_reg(smi, reg, &t); + if (err) { + len += snprintf(buf, sizeof(smi->buf), + "Read failed (reg: 0x%04x)\n", reg); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", + reg, t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_write_debugfs_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + unsigned long data; + u32 reg = smi->dbg_reg; + int err; + size_t len; + char *buf = smi->buf; + + len = min(count, sizeof(smi->buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + dev_err(smi->parent, "copy from user failed\n"); + return -EFAULT; + } + + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + + if (kstrtoul(buf, 16, &data)) { + dev_err(smi->parent, "Invalid reg value %s\n", buf); + } else { + err = rtl8366_smi_write_reg(smi, reg, data); + if (err) { + dev_err(smi->parent, + "writing reg 0x%04x val 0x%04lx failed\n", + reg, data); + } + } + + return count; +} + +static ssize_t rtl8366_read_debugfs_mibs(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = file->private_data; + int i, j, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", + "Counter"); + + for (i = 0; i < smi->num_ports; i++) { + char port_buf[10]; + + snprintf(port_buf, sizeof(port_buf), "Port %d", i); + len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", + port_buf); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + + for (i = 0; i < smi->num_mib_counters; i++) { + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", + smi->mib_counters[i].name); + for (j = 0; j < smi->num_ports; j++) { + unsigned long long counter = 0; + + if (!smi->ops->get_mib_counter(smi, i, j, &counter)) + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12llu ", counter); + else + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12s ", "error"); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_rtl8366_regs = { + .read = rtl8366_read_debugfs_reg, + .write = rtl8366_write_debugfs_reg, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_mc = { + .read = rtl8366_read_debugfs_vlan_mc, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_4k = { + .read = rtl8366_read_debugfs_vlan_4k, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_pvid = { + .read = rtl8366_read_debugfs_pvid, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_mibs = { + .read = rtl8366_read_debugfs_mibs, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static void rtl8366_debugfs_init(struct rtl8366_smi *smi) +{ + struct dentry *node; + struct dentry *root; + + if (!smi->debugfs_root) + smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), + NULL); + + if (!smi->debugfs_root) { + dev_err(smi->parent, "Unable to create debugfs dir\n"); + return; + } + root = smi->debugfs_root; + + node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, + &smi->dbg_reg); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "reg"); + return; + } + + node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, + &fops_rtl8366_regs); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "val"); + return; + } + + node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, + &fops_rtl8366_vlan_mc); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_mc"); + return; + } + + node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, + &smi->dbg_vlan_4k_page); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k_page"); + return; + } + + node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, + &fops_rtl8366_vlan_4k); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k"); + return; + } + + node = debugfs_create_file("pvid", S_IRUSR, root, smi, + &fops_rtl8366_pvid); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "pvid"); + return; + } + + node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, + &fops_rtl8366_mibs); + if (!node) + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "mibs"); +} + +static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) +{ + if (smi->debugfs_root) { + debugfs_remove_recursive(smi->debugfs_root); + smi->debugfs_root = NULL; + } +} +#else +static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} +static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} +#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + +#ifdef CONFIG_OF + struct device_node *np = NULL; + + np = of_get_child_by_name(smi->parent->of_node, "mdio-bus"); +#endif + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = dev_name(smi->parent); + smi->mii_bus->read = smi->ops->mii_read; + smi->mii_bus->write = smi->ops->mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(smi->parent)); + smi->mii_bus->parent = smi->parent; + smi->mii_bus->phy_mask = ~(0x1f); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) + { + int i; + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + } +#endif + +#ifdef CONFIG_OF + if (np) + ret = of_mdiobus_register(smi->mii_bus, np); + else +#endif + ret = mdiobus_register(smi->mii_bus); + + if (ret) + goto err_free; + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + err = rtl8366_reset(smi); + if (err) + return err; + + err = smi->ops->setup(smi); + if (err) + return err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + err = rtl8366_enable_vlan(smi, 1); + if (err) + return err; + + return rtl8366_enable_all_ports(smi, 1); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); + +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_get_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); + +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_set_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); + +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int i, len = 0; + unsigned long long counter = 0; + char *buf = smi->buf; + + if (val->port_vlan >= smi->num_ports) + return -EINVAL; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "Port %d MIB counters\n", + val->port_vlan); + + for (i = 0; i < smi->num_mib_counters; ++i) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%-36s: ", smi->mib_counters[i].name); + if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, + &counter)) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%llu\n", counter); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%s\n", "error"); + } + + val->value.s = buf; + val->len = len; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); + +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + unsigned long long counter = 0; + int ret; + + if (port >= smi->num_ports) + return -EINVAL; + + ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter); + if (ret) + return ret; + + stats->tx_bytes = counter; + + ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter); + if (ret) + return ret; + + stats->rx_bytes = counter; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats); + +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int i; + u32 len = 0; + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char *buf = smi->buf; + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + memset(buf, '\0', sizeof(smi->buf)); + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "VLAN %d: Ports: '", vlan4k.vid); + + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & (1 << i))) + continue; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, + (vlan4k.untag & (1 << i)) ? "" : "t"); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "', members=%04x, untag=%04x, fid=%u", + vlan4k.member, vlan4k.untag, vlan4k.fid); + + val->value.s = buf; + val->len = len; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); + +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + struct rtl8366_vlan_4k vlan4k; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + + port = &val->value.ports[0]; + val->len = 0; + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & BIT(i))) + continue; + + port->id = i; + port->flags = (vlan4k.untag & BIT(i)) ? + 0 : BIT(SWITCH_PORT_FLAG_TAGGED); + val->len++; + port++; + } + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); + +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + u32 member = 0; + u32 untag = 0; + int err; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + port = &val->value.ports[0]; + for (i = 0; i < val->len; i++, port++) { + int pvid = 0; + member |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) + untag |= BIT(port->id); + + /* + * To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + err = rtl8366_get_pvid(smi, port->id, &pvid); + if (err < 0) + return err; + if (pvid == 0) { + err = rtl8366_set_pvid(smi, port->id, val->port_vlan); + if (err < 0) + return err; + } + } + + return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); + +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + val->value.i = vlan4k.fid; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); + +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + if (val->value.i < 0 || val->value.i > attr->max) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + return rtl8366_set_vlan(smi, val->port_vlan, + vlan4k.member, + vlan4k.untag, + val->value.i); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); + +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + val->value.i = smi->vlan_enabled; + else + val->value.i = smi->vlan4k_enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); + +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + err = rtl8366_enable_vlan(smi, val->value.i); + else + err = rtl8366_enable_vlan4k(smi, val->value.i); + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) +{ + struct rtl8366_smi *smi; + + BUG_ON(!parent); + + smi = kzalloc(sizeof(*smi), GFP_KERNEL); + if (!smi) { + dev_err(parent, "no memory for private data\n"); + return NULL; + } + + smi->parent = parent; + return smi; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); + +static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) +{ + int err; + + if (!smi->ext_mbus) { + err = gpio_request(smi->gpio_sda, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sda, err); + goto err_out; + } + + err = gpio_request(smi->gpio_sck, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sck, err); + goto err_free_sda; + } + } + + spin_lock_init(&smi->lock); + + /* start the switch */ + if (smi->hw_reset) { + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + } + + return 0; + + err_free_sda: + gpio_free(smi->gpio_sda); + err_out: + return err; +} + +static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) + smi->hw_reset(smi, true); + + if (!smi->ext_mbus) { + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); + } +} + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) +{ + static struct rtl8366_smi smi; + enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; + u32 reg = 0; + + memset(&smi, 0, sizeof(smi)); + smi.gpio_sda = pdata->gpio_sda; + smi.gpio_sck = pdata->gpio_sck; + smi.clk_delay = 10; + smi.cmd_read = 0xa9; + smi.cmd_write = 0xa8; + + if (__rtl8366_smi_init(&smi, "rtl8366")) + goto out; + + if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) + goto cleanup; + + switch(reg) { + case 0x6027: + printk("Found an RTL8366S switch\n"); + type = RTL8366_TYPE_S; + break; + case 0x5937: + printk("Found an RTL8366RB switch\n"); + type = RTL8366_TYPE_RB; + break; + default: + printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); + break; + } + +cleanup: + __rtl8366_smi_cleanup(&smi); +out: + return type; +} + +int rtl8366_smi_init(struct rtl8366_smi *smi) +{ + int err; + + if (!smi->ops) + return -EINVAL; + + err = __rtl8366_smi_init(smi, dev_name(smi->parent)); + if (err) + goto err_out; + + if (!smi->ext_mbus) + dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", + smi->gpio_sda, smi->gpio_sck); + else + dev_info(smi->parent, "using MDIO bus '%s'\n", smi->ext_mbus->name); + + err = smi->ops->detect(smi); + if (err) { + dev_err(smi->parent, "chip detection failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_reset(smi); + if (err) + goto err_free_sck; + + err = smi->ops->setup(smi); + if (err) { + dev_err(smi->parent, "chip setup failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_init_vlan(smi); + if (err) { + dev_err(smi->parent, "VLAN initialization failed, err=%d\n", + err); + goto err_free_sck; + } + + err = rtl8366_enable_all_ports(smi, 1); + if (err) + goto err_free_sck; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_free_sck; + + rtl8366_debugfs_init(smi); + + return 0; + + err_free_sck: + __rtl8366_smi_cleanup(smi); + err_out: + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_init); + +void rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + rtl8366_debugfs_remove(smi); + rtl8366_smi_mii_cleanup(smi); + __rtl8366_smi_cleanup(smi); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); + +#ifdef CONFIG_OF +static void rtl8366_smi_reset(struct rtl8366_smi *smi, bool active) +{ + if (active) + reset_control_assert(smi->reset); + else + reset_control_deassert(smi->reset); +} + +int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0); + int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0); + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio_node; + + mdio_node = of_parse_phandle(np, "mii-bus", 0); + if (!mdio_node) { + dev_err(&pdev->dev, "cannot find mdio node phandle"); + goto try_gpio; + } + + smi->ext_mbus = of_mdio_find_bus(mdio_node); + if (!smi->ext_mbus) { + dev_err(&pdev->dev, + "cannot find mdio bus from bus handle"); + goto try_gpio; + } + + return 0; + +try_gpio: + if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) { + dev_err(&pdev->dev, "gpios missing in devictree\n"); + return -EINVAL; + } + + smi->gpio_sda = sda; + smi->gpio_sck = sck; + smi->reset = devm_reset_control_get(&pdev->dev, "switch"); + if (!IS_ERR(smi->reset)) + smi->hw_reset = rtl8366_smi_reset; + + return 0; +} +#else +static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + return -ENODEV; +} +#endif + +int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata = pdev->dev.platform_data; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + return 0; +} + + +struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) + return NULL; + + if (pdev->dev.of_node) + err = rtl8366_smi_probe_of(pdev, smi); + else + err = rtl8366_smi_probe_plat(pdev, smi); + + if (err) + goto free_smi; + + return smi; + +free_smi: + kfree(smi); + return NULL; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_probe); + +MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.h b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.h new file mode 100644 index 0000000000..d1d988a372 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366_smi.h @@ -0,0 +1,160 @@ +/* + * Realtek RTL8366 SMI interface driver defines + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_SMI_H +#define _RTL8366_SMI_H + +#include +#include +#include +#include + +struct rtl8366_smi_ops; +struct rtl8366_vlan_ops; +struct mii_bus; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned base; + unsigned offset; + unsigned length; + const char *name; +}; + +struct rtl8366_smi { + struct device *parent; + unsigned int gpio_sda; + unsigned int gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + unsigned int clk_delay; /* ns */ + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + struct switch_dev sw_dev; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + struct rtl8366_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; + + struct reset_control *reset; + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS + struct dentry *debugfs_root; + u16 dbg_reg; + u8 dbg_vlan_4k_page; +#endif + struct mii_bus *ext_mbus; +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct rtl8366_smi_ops { + int (*detect)(struct rtl8366_smi *smi); + int (*reset_chip)(struct rtl8366_smi *smi); + int (*setup)(struct rtl8366_smi *smi); + + int (*mii_read)(struct mii_bus *bus, int addr, int reg); + int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); + + int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); + int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); + int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val); + int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); + int (*enable_vlan)(struct rtl8366_smi *smi, int enable); + int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); + int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); +}; + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); +int rtl8366_smi_init(struct rtl8366_smi *smi); +void rtl8366_smi_cleanup(struct rtl8366_smi *smi); +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi); +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file); +#endif + +static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) +{ + return container_of(sw, struct rtl8366_smi, sw_dev); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev); +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id); + +struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev); + +#endif /* _RTL8366_SMI_H */ diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8366rb.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366rb.c new file mode 100644 index 0000000000..dc394c02b6 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366rb.c @@ -0,0 +1,1532 @@ +/* + * Platform driver for the Realtek RTL8366RB ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" +#define RTL8366RB_DRIVER_VER "0.2.4" + +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_PAGE_MAX 7 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Port Mirror Control Register */ +#define RTL8366RB_PMCR 0x0007 +#define RTL8366RB_PMCR_SOURCE_PORT(_x) (_x) +#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f +#define RTL8366RB_PMCR_MONITOR_PORT(_x) ((_x) << 4) +#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 +#define RTL8366RB_PMCR_MIRROR_RX BIT(8) +#define RTL8366RB_PMCR_MIRROR_TX BIT(9) +#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) +#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW 1 +#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 + +#define RTL8366RB_PHY_CTRL_READ 1 +#define RTL8366RB_PHY_CTRL_WRITE 0 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_BIT 0 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + + +#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +#define RTL8366RB_VLAN_FID_MASK 0x7 + + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG_OFFSET 14 +#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG_OFFSET 9 +#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) + +#define RTL8366RB_BDTH_SW_MAX 1048512 +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS_BIT 15 +#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + + +#define RTL8366RB_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366RB_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_setup(struct rtl8366_smi *smi) +{ + int err; +#ifdef CONFIG_OF + unsigned i; + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; + + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) + return -EINVAL; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + rtl8366rb_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; + +} + +static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_GLOBAL_RESET); +} + +static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366RB_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, portmask); + + return 0; +} + +static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { + reg = RTL8366RB_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366RB_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 mask, data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + mask = 1 << val->port_vlan ; + if (val->value.i) + data = mask; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); +} + +static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); + if (data & (1 << val->port_vlan)) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), + RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, + val->value.i | + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); + +} + +static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); + data &= RTL8366RB_IB_BDTH_MASK; + if (data < RTL8366RB_IB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, + RTL8366RB_EB_PREIFG_MASK, + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), + RTL8366RB_EB_BDTH_MASK, val->value.i ); + +} + +static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); + data &= RTL8366RB_EB_BDTH_MASK; + if (data < RTL8366RB_EB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_QOS_MASK; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); +} + +static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); + if (data & RTL8366RB_QOS_MASK) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_RX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data); +} + +static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_RX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_TX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data); +} + +static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_TX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_ISO; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data); +} + +static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_ISO) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_SPC; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data); +} + +static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_SPC) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK; + + return 0; +} + +static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); +} + +static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366rb_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366rb_sw_set_learning_enable, + .get = rtl8366rb_sw_get_learning_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366rb_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366rb_sw_set_blinkrate, + .get = rtl8366rb_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_qos", + .description = "Enable QOS", + .set = rtl8366rb_sw_set_qos_enable, + .get = rtl8366rb_sw_get_qos_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = rtl8366rb_sw_set_mirror_rx_enable, + .get = rtl8366rb_sw_get_mirror_rx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = rtl8366rb_sw_set_mirror_tx_enable, + .get = rtl8366rb_sw_get_mirror_tx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_monitor_isolation", + .description = "Enable isolation of monitor port (TX packets will be dropped)", + .set = rtl8366rb_sw_set_monitor_isolation_enable, + .get = rtl8366rb_sw_get_monitor_isolation_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_pause_frames", + .description = "Enable mirroring of RX pause frames", + .set = rtl8366rb_sw_set_mirror_pause_frames_enable, + .get = rtl8366rb_sw_get_mirror_pause_frames_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = rtl8366rb_sw_set_mirror_monitor_port, + .get = rtl8366rb_sw_get_mirror_monitor_port, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = rtl8366rb_sw_set_mirror_source_port, + .get = rtl8366rb_sw_get_mirror_source_port, + .max = 5 + }, +}; + +static struct switch_attr rtl8366rb_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366rb_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366rb_sw_set_port_led, + .get = rtl8366rb_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "disable", + .description = "Get/Set port state (enabled or disabled)", + .max = 1, + .set = rtl8366rb_sw_set_port_disable, + .get = rtl8366rb_sw_get_port_disable, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_in", + .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_in, + .get = rtl8366rb_sw_get_port_rate_in, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_out", + .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_out, + .get = rtl8366rb_sw_get_port_rate_out, + }, +}; + +static struct switch_attr rtl8366rb_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366RB_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366rb_globals, + .n_attr = ARRAY_SIZE(rtl8366rb_globals), + }, + .attr_port = { + .attr = rtl8366rb_port, + .n_attr = ARRAY_SIZE(rtl8366rb_port), + }, + .attr_vlan = { + .attr = rtl8366rb_vlan, + .n_attr = ARRAY_SIZE(rtl8366rb_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366rb_sw_get_port_link, + .get_port_stats = rtl8366rb_sw_get_port_stats, +}; + +static int rtl8366rb_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366RB"; + dev->cpu_port = RTL8366RB_PORT_NUM_CPU; + dev->ports = RTL8366RB_NUM_PORTS; + dev->vlans = RTL8366RB_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366rb_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .reset_chip = rtl8366rb_reset_chip, + .setup = rtl8366rb_setup, + + .mii_read = rtl8366rb_mii_read, + .mii_write = rtl8366rb_mii_write, + + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .enable_port = rtl8366rb_enable_port, +}; + +static int rtl8366rb_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366RB_DRIVER_DESC + " version " RTL8366RB_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366rb_smi_ops; + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366rb_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366rb_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366rb_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366rb_match[] = { + { .compatible = "realtek,rtl8366rb" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366rb_match); +#endif + +static struct platform_driver rtl8366rb_driver = { + .driver = { + .name = RTL8366RB_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rtl8366rb_match), + }, + .probe = rtl8366rb_probe, + .remove = rtl8366rb_remove, +}; + +static int __init rtl8366rb_module_init(void) +{ + return platform_driver_register(&rtl8366rb_driver); +} +module_init(rtl8366rb_module_init); + +static void __exit rtl8366rb_module_exit(void) +{ + platform_driver_unregister(&rtl8366rb_driver); +} +module_exit(rtl8366rb_module_exit); + +MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); +MODULE_VERSION(RTL8366RB_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_AUTHOR("Roman Yeryomin "); +MODULE_AUTHOR("Colin Leitner "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8366s.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366s.c new file mode 100644 index 0000000000..3f458f9dec --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8366s.c @@ -0,0 +1,1320 @@ +/* + * Platform driver for the Realtek RTL8366S ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" +#define RTL8366S_DRIVER_VER "0.2.2" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366S_SGCR 0x0000 +#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) +#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) +#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) +#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_EN_VLAN BIT(13) + +/* Port Enable Control register */ +#define RTL8366S_PECR 0x0001 + +/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */ +#define RTL8366S_GREEN_ETHERNET_CTRL_REG 0x000a +#define RTL8366S_GREEN_ETHERNET_CTRL_MASK 0x0018 +#define RTL8366S_GREEN_ETHERNET_TX_BIT (1 << 3) +#define RTL8366S_GREEN_ETHERNET_RX_BIT (1 << 4) + +/* Switch Security Control registers */ +#define RTL8366S_SSCR0 0x0002 +#define RTL8366S_SSCR1 0x0003 +#define RTL8366S_SSCR2 0x0004 +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366S_RESET_CTRL_REG 0x0100 +#define RTL8366S_CHIP_CTRL_RESET_HW 1 +#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +/* Green Ethernet Feature for PHY ports */ +#define RTL8366S_PHY_POWER_SAVING_CTRL_REG 12 +#define RTL8366S_PHY_POWER_SAVING_MASK 0x1000 + +/* LED control registers */ +#define RTL8366S_LED_BLINKRATE_REG 0x0420 +#define RTL8366S_LED_BLINKRATE_BIT 0 +#define RTL8366S_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366S_LED_CTRL_REG 0x0421 +#define RTL8366S_LED_0_1_CTRL_REG 0x0422 +#define RTL8366S_LED_2_3_CTRL_REG 0x0423 + +#define RTL8366S_MIB_COUNT 33 +#define RTL8366S_GLOBAL_MIB_COUNT 1 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 +#define RTL8366S_MIB_COUNTER_BASE 0x1000 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 +#define RTL8366S_MIB_COUNTER_BASE2 0x1180 +#define RTL8366S_MIB_CTRL_REG 0x11F0 +#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF +#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 +#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 + +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC + + +#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 +#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B +#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366S_VLAN_TB_CTRL_REG 0x010F + +#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) + +#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 + +#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 +#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366S_PORT_NUM_CPU 5 +#define RTL8366S_NUM_PORTS 6 +#define RTL8366S_NUM_VLANS 16 +#define RTL8366S_NUM_LEDGROUPS 4 +#define RTL8366S_NUM_VIDS 4096 +#define RTL8366S_PRIORITYMAX 7 +#define RTL8366S_FIDMAX 7 + + +#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ + +#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ +#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN) + +#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4) + +#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_VLAN_VID_MASK 0xfff +#define RTL8366S_VLAN_PRIORITY_SHIFT 12 +#define RTL8366S_VLAN_PRIORITY_MASK 0x7 +#define RTL8366S_VLAN_MEMBER_MASK 0x3f +#define RTL8366S_VLAN_UNTAG_SHIFT 6 +#define RTL8366S_VLAN_UNTAG_MASK 0x3f +#define RTL8366S_VLAN_FID_SHIFT 12 +#define RTL8366S_VLAN_FID_MASK 0x7 + +#define RTL8366S_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366S_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + + /* + * The following counters are accessible at a different + * base address. + */ + { 1, 0, 2, "Dot1dTpPortInDiscards" }, + { 1, 2, 2, "IfOutUcastPkts" }, + { 1, 4, 2, "IfOutMulticastPkts" }, + { 1, 6, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366s_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, + RTL8366S_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + u32 phyData; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + if (enable) + phyData |= RTL8366S_PHY_POWER_SAVING_MASK; + else + phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK; + + err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData); + if (err) + return err; + + return 0; +} + +static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable) +{ + int err; + unsigned i; + u32 data = 0; + + if (!enable) { + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + rtl8366s_set_green_port(smi, i, 0); + } + } + + if (enable) + data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT); + + REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data); + + return 0; +} + +static int rtl8366s_setup(struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata; + int err; + unsigned i; +#ifdef CONFIG_OF + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; +#endif + + pdata = smi->parent->platform_data; + if (pdata && pdata->num_initvals && pdata->initvals) { + dev_info(smi->parent, "applying initvals\n"); + for (i = 0; i < pdata->num_initvals; i++) + REG_WR(smi, pdata->initvals[i].reg, + pdata->initvals[i].val); + } + +#ifdef CONFIG_OF + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } + + if (of_property_read_bool(np, "realtek,green-ethernet-features")) { + dev_info(smi->parent, "activating Green Ethernet features\n"); + + err = rtl8366s_set_green(smi, 1); + if (err) + return err; + + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + err = rtl8366s_set_green_port(smi, i, 1); + if (err) + return err; + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, + RTL8366S_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) + return -EINVAL; + + switch (rtl8366s_mib_counters[counter].base) { + case 0: + addr = RTL8366S_MIB_COUNTER_BASE + + RTL8366S_MIB_COUNTER_PORT_OFFSET * port; + break; + + case 1: + addr = RTL8366S_MIB_COUNTER_BASE2 + + RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; + break; + + default: + return -EINVAL; + } + + addr += rtl8366s_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366S_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366S_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366S_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, + vid & RTL8366S_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + if (vlan4k->vid >= RTL8366S_NUM_VIDS || + vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & + RTL8366S_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + if (index >= RTL8366S_NUM_VLANS || + vlanmc->vid >= RTL8366S_NUM_VIDS || + vlanmc->priority > RTL8366S_PRIORITYMAX || + vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << + RTL8366S_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366S_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + RTL8366S_PORT_VLAN_CTRL_MASK << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366S_PORT_VLAN_CTRL_MASK) << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, + (enable) ? RTL8366S_SGCR_EN_VLAN : 0); +} + +static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, + 1, (enable) ? 1 : 0); +} + +static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366S_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366S_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); +} + +static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, + RTL8366S_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366s_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); + + val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); + + return 0; +} + +static int rtl8366s_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char length_code; + + switch (val->value.i) { + case 0: + length_code = RTL8366S_SGCR_MAX_LENGTH_1522; + break; + case 1: + length_code = RTL8366S_SGCR_MAX_LENGTH_1536; + break; + case 2: + length_code = RTL8366S_SGCR_MAX_LENGTH_1552; + break; + case 3: + length_code = RTL8366S_SGCR_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, + RTL8366S_SGCR_MAX_LENGTH_MASK, + length_code); +} + +static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366S_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, portmask); + + return 0; +} + +static int rtl8366s_sw_get_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + int err; + + err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data); + if (err) + return err; + + val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366s_set_green(smi, val->value.i); +} + +static int rtl8366s_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366s_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366S_NUM_PORTS || + (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) + return -EINVAL; + + if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { + reg = RTL8366S_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366S_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366s_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366s_sw_get_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + u32 phyData; + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i); +} + +static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, + 0, (1 << (val->port_vlan + 3))); +} + +static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366s_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366s_sw_set_learning_enable, + .get = rtl8366s_sw_get_learning_enable, + .max = 1, + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366s_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366s_sw_set_blinkrate, + .get = rtl8366s_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", + .set = rtl8366s_sw_set_max_length, + .get = rtl8366s_sw_get_max_length, + .max = 3, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_mode", + .description = "Get/Set the router green feature", + .set = rtl8366s_sw_set_green, + .get = rtl8366s_sw_get_green, + .max = 1, + }, +}; + +static struct switch_attr rtl8366s_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366s_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366s_sw_set_port_led, + .get = rtl8366s_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_port", + .description = "Get/Set port green feature (0 - 1)", + .max = 1, + .set = rtl8366s_sw_set_green_port, + .get = rtl8366s_sw_get_green_port, + }, +}; + +static struct switch_attr rtl8366s_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366S_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366s_globals, + .n_attr = ARRAY_SIZE(rtl8366s_globals), + }, + .attr_port = { + .attr = rtl8366s_port, + .n_attr = ARRAY_SIZE(rtl8366s_port), + }, + .attr_vlan = { + .attr = rtl8366s_vlan, + .n_attr = ARRAY_SIZE(rtl8366s_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366s_sw_get_port_link, + .get_port_stats = rtl8366s_sw_get_port_stats, +}; + +static int rtl8366s_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366S"; + dev->cpu_port = RTL8366S_PORT_NUM_CPU; + dev->ports = RTL8366S_NUM_PORTS; + dev->vlans = RTL8366S_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366s_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366s_smi_ops = { + .detect = rtl8366s_detect, + .reset_chip = rtl8366s_reset_chip, + .setup = rtl8366s_setup, + + .mii_read = rtl8366s_mii_read, + .mii_write = rtl8366s_mii_write, + + .get_vlan_mc = rtl8366s_get_vlan_mc, + .set_vlan_mc = rtl8366s_set_vlan_mc, + .get_vlan_4k = rtl8366s_get_vlan_4k, + .set_vlan_4k = rtl8366s_set_vlan_4k, + .get_mc_index = rtl8366s_get_mc_index, + .set_mc_index = rtl8366s_set_mc_index, + .get_mib_counter = rtl8366_get_mib_counter, + .is_vlan_valid = rtl8366s_is_vlan_valid, + .enable_vlan = rtl8366s_enable_vlan, + .enable_vlan4k = rtl8366s_enable_vlan4k, + .enable_port = rtl8366s_enable_port, +}; + +static int rtl8366s_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366S_DRIVER_DESC + " version " RTL8366S_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366s_smi_ops; + smi->cpu_port = RTL8366S_PORT_NUM_CPU; + smi->num_ports = RTL8366S_NUM_PORTS; + smi->num_vlan_mc = RTL8366S_NUM_VLANS; + smi->mib_counters = rtl8366s_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366s_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366s_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366s_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366s_match[] = { + { .compatible = "realtek,rtl8366s" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366s_match); +#endif + +static struct platform_driver rtl8366s_driver = { + .driver = { + .name = RTL8366S_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8366s_match), +#endif + }, + .probe = rtl8366s_probe, + .remove = rtl8366s_remove, +}; + +static int __init rtl8366s_module_init(void) +{ + return platform_driver_register(&rtl8366s_driver); +} +module_init(rtl8366s_module_init); + +static void __exit rtl8366s_module_exit(void) +{ + platform_driver_unregister(&rtl8366s_driver); +} +module_exit(rtl8366s_module_exit); + +MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); +MODULE_VERSION(RTL8366S_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8367.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8367.c new file mode 100644 index 0000000000..9549961d76 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8367.c @@ -0,0 +1,1846 @@ +/* + * Platform driver for the Realtek RTL8367R/M ethernet switches + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367_PHY_ADDR_MAX 8 +#define RTL8367_PHY_REG_MAX 31 + +#define RTL8367_VID_MASK 0xffff +#define RTL8367_FID_MASK 0xfff +#define RTL8367_UNTAG_MASK 0xffff +#define RTL8367_MEMBER_MASK 0xffff + +#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 +#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367_TA_CTRL_REG 0x0500 +#define RTL8367_TA_CTRL_STATUS BIT(12) +#define RTL8367_TA_CTRL_METHOD BIT(5) +#define RTL8367_TA_CTRL_CMD_SHIFT 4 +#define RTL8367_TA_CTRL_CMD_READ 0 +#define RTL8367_TA_CTRL_CMD_WRITE 1 +#define RTL8367_TA_CTRL_TABLE_SHIFT 0 +#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367_TA_CTRL_TABLE_L2 4 +#define RTL8367_TA_CTRL_CVLAN_READ \ + ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) +#define RTL8367_TA_CTRL_CVLAN_WRITE \ + ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) + +#define RTL8367_TA_ADDR_REG 0x0501 +#define RTL8367_TA_ADDR_MASK 0x3fff + +#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) +#define RTL8367_TA_VLAN_DATA_SIZE 4 +#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK +#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 +#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_TA_VLAN_FID_SHIFT 0 +#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK +#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 +#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 +#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 +#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff + +#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) +#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f +#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) + +#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) +#define RTL8367_VLAN_MC_DATA_SIZE 4 +#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 +#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_VLAN_MC_FID_SHIFT 0 +#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK +#define RTL8367_VLAN_MC_EVID_SHIFT 0 +#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK + +#define RTL8367_VLAN_CTRL_REG 0x07a8 +#define RTL8367_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367_VLAN_INGRESS_REG 0x07a9 + +#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) + +#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) + +#define RTL8367_MIB_ADDRESS_REG 0x1004 + +#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) +#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) +#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) +#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) +#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) + +#define RTL8367_MIB_COUNT 36 +#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 + +#define RTL8367_SWC0_REG 0x1200 +#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 +#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) +#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) +#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) +#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) +#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) +#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) + +#define RTL8367_CHIP_NUMBER_REG 0x1300 + +#define RTL8367_CHIP_VER_REG 0x1301 +#define RTL8367_CHIP_VER_RLVID_SHIFT 12 +#define RTL8367_CHIP_VER_RLVID_MASK 0xf +#define RTL8367_CHIP_VER_MCID_SHIFT 8 +#define RTL8367_CHIP_VER_MCID_MASK 0xf +#define RTL8367_CHIP_VER_BOID_SHIFT 4 +#define RTL8367_CHIP_VER_BOID_MASK 0xf + +#define RTL8367_CHIP_MODE_REG 0x1302 +#define RTL8367_CHIP_MODE_MASK 0x7 + +#define RTL8367_CHIP_DEBUG0_REG 0x1303 +#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367_DIS_REG 0x1305 +#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367_DIS_RGMII_MASK 0x7 + +#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367_DI_FORCE_MODE BIT(12) +#define RTL8367_DI_FORCE_NWAY BIT(7) +#define RTL8367_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367_DI_FORCE_LINK BIT(4) +#define RTL8367_DI_FORCE_DUPLEX BIT(2) +#define RTL8367_DI_FORCE_SPEED_MASK 3 +#define RTL8367_DI_FORCE_SPEED_10 0 +#define RTL8367_DI_FORCE_SPEED_100 1 +#define RTL8367_DI_FORCE_SPEED_1000 2 + +#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367_CHIP_RESET_REG 0x1322 +#define RTL8367_CHIP_RESET_SW BIT(1) +#define RTL8367_CHIP_RESET_HW BIT(0) + +#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) +#define RTL8367_PORT_STATUS_NWAY BIT(7) +#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) +#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) +#define RTL8367_PORT_STATUS_LINK BIT(4) +#define RTL8367_PORT_STATUS_DUPLEX BIT(2) +#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8367_PORT_STATUS_SPEED_10 0 +#define RTL8367_PORT_STATUS_SPEED_100 1 +#define RTL8367_PORT_STATUS_SPEED_1000 2 + +#define RTL8367_RTL_NO_REG 0x13c0 +#define RTL8367_RTL_NO_8367R 0x3670 +#define RTL8367_RTL_NO_8367M 0x3671 + +#define RTL8367_RTL_VER_REG 0x13c1 +#define RTL8367_RTL_VER_MASK 0xf + +#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 +#define RTL8367_LED_MODE_REG 0x1b02 +#define RTL8367_LED_MODE_RATE_M 0x7 +#define RTL8367_LED_MODE_RATE_S 1 + +#define RTL8367_LED_CONFIG_REG 0x1b03 +#define RTL8367_LED_CONFIG_DATA_S 12 +#define RTL8367_LED_CONFIG_DATA_M 0x3 +#define RTL8367_LED_CONFIG_SEL BIT(14) +#define RTL8367_LED_CONFIG_LED_CFG_M 0xf + +#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 +#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 +#define RTL8367_PARA_LED_IO_EN_PMASK 0xff + +#define RTL8367_IA_CTRL_REG 0x1f00 +#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) +#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) +#define RTL8367_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367_IA_STATUS_REG 0x1f01 +#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367_IA_ADDRESS_REG 0x1f02 + +#define RTL8367_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367_IA_READ_DATA_REG 0x1f04 + +#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367_CPU_PORT_NUM 9 +#define RTL8367_NUM_PORTS 10 +#define RTL8367_NUM_VLANS 32 +#define RTL8367_NUM_LEDGROUPS 4 +#define RTL8367_NUM_VIDS 4096 +#define RTL8367_PRIORITYMAX 7 +#define RTL8367_FIDMAX 7 + +#define RTL8367_PORT_0 BIT(0) +#define RTL8367_PORT_1 BIT(1) +#define RTL8367_PORT_2 BIT(2) +#define RTL8367_PORT_3 BIT(3) +#define RTL8367_PORT_4 BIT(4) +#define RTL8367_PORT_5 BIT(5) +#define RTL8367_PORT_6 BIT(6) +#define RTL8367_PORT_7 BIT(7) +#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ +#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ + +#define RTL8367_PORTS_ALL \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ + RTL8367_PORT_E0) + +#define RTL8367_PORTS_ALL_BUT_CPU \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) + +struct rtl8367_initval { + u16 reg; + u16 val; +}; + +#define RTL8367_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8367_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 2, "Dot3StatsFCSErrors" }, + { 0, 6, 2, "Dot3StatsSymbolErrors" }, + { 0, 8, 2, "Dot3InPauseFrames" }, + { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 12, 2, "EtherStatsFragments" }, + { 0, 14, 2, "EtherStatsJabbers" }, + { 0, 16, 2, "IfInUcastPkts" }, + { 0, 18, 2, "EtherStatsDropEvents" }, + { 0, 20, 4, "EtherStatsOctets" }, + + { 0, 24, 2, "EtherStatsUnderSizePkts" }, + { 0, 26, 2, "EtherOversizeStats" }, + { 0, 28, 2, "EtherStatsPkts64Octets" }, + { 0, 30, 2, "EtherStatsPkts65to127Octets" }, + { 0, 32, 2, "EtherStatsPkts128to255Octets" }, + { 0, 34, 2, "EtherStatsPkts256to511Octets" }, + { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 40, 2, "EtherStatsMulticastPkts" }, + { 0, 42, 2, "EtherStatsBroadcastPkts" }, + + { 0, 44, 4, "IfOutOctets" }, + + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, + { 0, 72, 2, "OutOampduPkts" }, + { 0, 74, 2, "InOampduPkts" }, + { 0, 76, 2, "PktgenPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367_initval rtl8367_initvals_0_0[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, + {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, + {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, + {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, + {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, + {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, + {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, + {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, + {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, + {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, +}; + +static const struct rtl8367_initval rtl8367_initvals_0_1[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, + {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, + {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, + {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, + {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, + {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, + {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, + {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, + {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, + {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_0[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, + {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, + {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, + {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, + {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, + {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, + {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, + {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, + {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, + {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_1[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, + {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, + {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, + {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, + {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, + {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, + {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, + {0x1B03, 0x0876}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_0[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, + {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, + {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, + {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, + {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, + {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, + {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, + {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, + {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, + {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, + {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, + {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, + {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_1[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, + {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, + {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, + {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, + {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, + {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x130c, 0x0050}, +}; + +static int rtl8367_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + int err; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_0_0; + count = ARRAY_SIZE(rtl8367_initvals_0_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_0_1; + count = ARRAY_SIZE(rtl8367_initvals_0_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + err = rtl8367_write_initvals(smi, initvals, count); + if (err) + return err; + + /* TODO: complete this */ + + return 0; +} + +static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_1_0; + count = ARRAY_SIZE(rtl8367_initvals_1_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_1_1; + count = ARRAY_SIZE(rtl8367_initvals_1_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_2_0; + count = ARRAY_SIZE(rtl8367_initvals_2_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_2_1; + count = ARRAY_SIZE(rtl8367_initvals_2_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs(struct rtl8366_smi *smi) +{ + u32 data; + u32 rlvid; + u32 mode; + int err; + + REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); + + REG_RD(smi, RTL8367_CHIP_VER_REG, &data); + rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & + RTL8367_CHIP_VER_RLVID_MASK; + + REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); + mode = data & RTL8367_CHIP_MODE_MASK; + + switch (rlvid) { + case 0: + err = rtl8367_init_regs0(smi, mode); + break; + + case 1: + err = rtl8367_write_phy_reg(smi, 0, 31, 5); + if (err) + break; + + err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); + if (err) + break; + + err = rtl8367_read_phy_reg(smi, 0, 6, &data); + if (err) + break; + + if (data == 0x94eb) { + err = rtl8367_init_regs1(smi, mode); + } else if (data == 0x2104) { + err = rtl8367_init_regs2(smi, mode); + } else { + dev_err(smi->parent, "unknow phy data %04x\n", data); + return -ENODEV; + } + + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + err = -ENODEV; + break; + } + + return err; +} + +static int rtl8367_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); + msleep(RTL8367_RESET_DELAY); + + do { + REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); + if (!(data & RTL8367_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, + RTL8367_CHIP_DEBUG0_DUMMY0(id), + RTL8367_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367_DIS_REG, + RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), + mode << RTL8367_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_DI_FORCE_MODE | + RTL8367_DI_FORCE_NWAY | + RTL8367_DI_FORCE_TXPAUSE | + RTL8367_DI_FORCE_RXPAUSE | + RTL8367_DI_FORCE_LINK | + RTL8367_DI_FORCE_DUPLEX | + RTL8367_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | + (RTL8367_EXT_RGMXF_TXDELAY_MASK << + RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, + unsigned int group, u16 port_mask) +{ + u32 reg; + u32 s; + int err; + + port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; + s = (group % 2) * 8; + reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); + + REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); + + return 0; +} + +static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, + unsigned int mode) +{ + u16 mask; + u16 set; + int err; + + mode &= RTL8367_LED_CONFIG_DATA_M; + + mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | + RTL8367_LED_CONFIG_SEL; + set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + + return 0; +} + +static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, + unsigned int led, unsigned int cfg) +{ + u16 mask; + u16 set; + int err; + + mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | + RTL8367_LED_CONFIG_SEL; + set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + return 0; +} + +static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) +{ + int err; + + REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); + return 0; +} + +static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) +{ + u16 mask; + u16 set; + int err; + + mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; + set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; + REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, + RTL8367_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367_PORT_CFG_REG(i), + RTL8367_PORT_CFG_EGRESS_MODE_MASK << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, + RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); + + /* setup LEDs */ + err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); + if (err) + return err; + + err = rtl8367_led_group_set_mode(smi, 0); + if (err) + return err; + + err = rtl8367_led_op_select_parallel(smi); + if (err) + return err; + + err = rtl8367_led_blinkrate_set(smi, 1); + if (err) + return err; + + err = rtl8367_led_group_set_config(smi, 0, 2); + if (err) + return err; + + return 0; +} + +static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) + return -EINVAL; + + mib = &rtl8367_mib_counters[counter]; + addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); + + if (data & RTL8367_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367_MIB_CTRL_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & + RTL8367_TA_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & + RTL8367_TA_VLAN_FID_MASK; + vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & + RTL8367_TA_VLAN_UNTAG1_MASK; + vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & + RTL8367_TA_VLAN_UNTAG2_MASK) << 2; + + return 0; +} + +static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + if (vlan4k->vid >= RTL8367_NUM_VIDS || + vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8367_UNTAG_MASK || + vlan4k->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << + RTL8367_TA_VLAN_MEMBER_SHIFT; + data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << + RTL8367_TA_VLAN_FID_SHIFT; + data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << + RTL8367_TA_VLAN_UNTAG1_SHIFT; + data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << + RTL8367_TA_VLAN_UNTAG2_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, + vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & + RTL8367_VLAN_MC_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & + RTL8367_VLAN_MC_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & + RTL8367_VLAN_MC_EVID_MASK; + + return 0; +} + +static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + if (index >= RTL8367_NUM_VLANS || + vlanmc->vid >= RTL8367_NUM_VIDS || + vlanmc->priority > RTL8367_PRIORITYMAX || + vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || + vlanmc->untag > RTL8367_UNTAG_MASK || + vlanmc->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << + RTL8367_VLAN_MC_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << + RTL8367_VLAN_MC_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << + RTL8367_VLAN_MC_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), + RTL8367_VLAN_PVID_CTRL_MASK << + RTL8367_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367_VLAN_PVID_CTRL_MASK) << + RTL8367_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, + RTL8367_VLAN_CTRL_ENABLE, + (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), + (enable) ? RTL8367_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, + RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); +} + +static int rtl8367_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); + + speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); + val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> + RTL8367_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, + RTL8367_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, + RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367_sw_set_max_length, + .get = rtl8367_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8367_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8367_sw_ops = { + .attr_global = { + .attr = rtl8367_globals, + .n_attr = ARRAY_SIZE(rtl8367_globals), + }, + .attr_port = { + .attr = rtl8367_port, + .n_attr = ARRAY_SIZE(rtl8367_port), + }, + .attr_vlan = { + .attr = rtl8367_vlan, + .n_attr = ARRAY_SIZE(rtl8367_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367_sw_get_port_link, + .get_port_stats = rtl8367_sw_get_port_stats, +}; + +static int rtl8367_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367"; + dev->cpu_port = RTL8367_CPU_PORT_NUM; + dev->ports = RTL8367_NUM_PORTS; + dev->vlans = RTL8367_NUM_VIDS; + dev->ops = &rtl8367_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367_detect(struct rtl8366_smi *smi) +{ + u32 rtl_no = 0; + u32 rtl_ver = 0; + char *chip_name; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); + if (ret) { + dev_err(smi->parent, "unable to read chip number\n"); + return ret; + } + + switch (rtl_no) { + case RTL8367_RTL_NO_8367R: + chip_name = "8367R"; + break; + case RTL8367_RTL_NO_8367M: + chip_name = "8367M"; + break; + default: + dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%s ver. %u chip found\n", + chip_name, rtl_ver & RTL8367_RTL_VER_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367_smi_ops = { + .detect = rtl8367_detect, + .reset_chip = rtl8367_reset_chip, + .setup = rtl8367_setup, + + .mii_read = rtl8367_mii_read, + .mii_write = rtl8367_mii_write, + + .get_vlan_mc = rtl8367_get_vlan_mc, + .set_vlan_mc = rtl8367_set_vlan_mc, + .get_vlan_4k = rtl8367_get_vlan_4k, + .set_vlan_4k = rtl8367_set_vlan_4k, + .get_mc_index = rtl8367_get_mc_index, + .set_mc_index = rtl8367_set_mc_index, + .get_mib_counter = rtl8367_get_mib_counter, + .is_vlan_valid = rtl8367_is_vlan_valid, + .enable_vlan = rtl8367_enable_vlan, + .enable_vlan4k = rtl8367_enable_vlan4k, + .enable_port = rtl8367_enable_port, +}; + +static int rtl8367_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367_smi_ops; + smi->cpu_port = RTL8367_CPU_PORT_NUM; + smi->num_ports = RTL8367_NUM_PORTS; + smi->num_vlan_mc = RTL8367_NUM_VLANS; + smi->mib_counters = rtl8367_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367_match[] = { + { .compatible = "realtek,rtl8367" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367_match); +#endif + +static struct platform_driver rtl8367_driver = { + .driver = { + .name = RTL8367_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367_match), +#endif + }, + .probe = rtl8367_probe, + .remove = rtl8367_remove, + .shutdown = rtl8367_shutdown, +}; + +static int __init rtl8367_module_init(void) +{ + return platform_driver_register(&rtl8367_driver); +} +module_init(rtl8367_module_init); + +static void __exit rtl8367_module_exit(void) +{ + platform_driver_unregister(&rtl8367_driver); +} +module_exit(rtl8367_module_exit); + +MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/rtl8367b.c b/target/linux/generic/files-4.14/drivers/net/phy/rtl8367b.c new file mode 100644 index 0000000000..e6ea6509ae --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/rtl8367b.c @@ -0,0 +1,1612 @@ +/* + * Platform driver for the Realtek RTL8367R-VB ethernet switches + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367B_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367B_PHY_ADDR_MAX 8 +#define RTL8367B_PHY_REG_MAX 31 + +#define RTL8367B_VID_MASK 0x3fff +#define RTL8367B_FID_MASK 0xf +#define RTL8367B_UNTAG_MASK 0xff +#define RTL8367B_MEMBER_MASK 0xff + +#define RTL8367B_PORT_MISC_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI 2 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367B_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367B_TA_CTRL_REG 0x0500 /*GOOD*/ +#define RTL8367B_TA_CTRL_SPA_SHIFT 8 +#define RTL8367B_TA_CTRL_SPA_MASK 0x7 +#define RTL8367B_TA_CTRL_METHOD BIT(4)/*GOOD*/ +#define RTL8367B_TA_CTRL_CMD_SHIFT 3 +#define RTL8367B_TA_CTRL_CMD_READ 0 +#define RTL8367B_TA_CTRL_CMD_WRITE 1 +#define RTL8367B_TA_CTRL_TABLE_SHIFT 0 /*GOOD*/ +#define RTL8367B_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367B_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367B_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367B_TA_CTRL_TABLE_L2 4 +#define RTL8367B_TA_CTRL_CVLAN_READ \ + ((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) +#define RTL8367B_TA_CTRL_CVLAN_WRITE \ + ((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) + +#define RTL8367B_TA_ADDR_REG 0x0501/*GOOD*/ +#define RTL8367B_TA_ADDR_MASK 0x3fff/*GOOD*/ + +#define RTL8367B_TA_LUT_REG 0x0502/*GOOD*/ + +#define RTL8367B_TA_WRDATA_REG(_x) (0x0510 + (_x))/*GOOD*/ +#define RTL8367B_TA_VLAN_NUM_WORDS 2 +#define RTL8367B_TA_VLAN_VID_MASK RTL8367B_VID_MASK +#define RTL8367B_TA_VLAN0_MEMBER_SHIFT 0 +#define RTL8367B_TA_VLAN0_MEMBER_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN0_UNTAG_SHIFT 8 +#define RTL8367B_TA_VLAN0_UNTAG_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN1_FID_SHIFT 0 +#define RTL8367B_TA_VLAN1_FID_MASK RTL8367B_FID_MASK + +#define RTL8367B_TA_RDDATA_REG(_x) (0x0520 + (_x))/*GOOD*/ + +#define RTL8367B_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_MASK 0x1f /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) /*GOOD*/ + +#define RTL8367B_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) /*GOOD*/ +#define RTL8367B_VLAN_MC_NUM_WORDS 4 /*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_MASK RTL8367B_MEMBER_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_MASK RTL8367B_FID_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_MASK RTL8367B_VID_MASK/*GOOD*/ + +#define RTL8367B_VLAN_CTRL_REG 0x07a8 /*GOOD*/ +#define RTL8367B_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367B_VLAN_INGRESS_REG 0x07a9 /*GOOD*/ + +#define RTL8367B_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) /*GOOD*/ + +#define RTL8367B_MIB_COUNTER_REG(_x) (0x1000 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_COUNTER_PORT_OFFSET 0x007c /*GOOD*/ + +#define RTL8367B_MIB_ADDRESS_REG 0x1004 /*GOOD*/ + +#define RTL8367B_MIB_CTRL0_REG(_x) (0x1005 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK BIT(11) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_QM_RESET_MASK BIT(10) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_RESET_MASK BIT(1) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_BUSY_MASK BIT(0) /*GOOD*/ + +#define RTL8367B_SWC0_REG 0x1200/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_SHIFT 13/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH(_x) ((_x) << 13) /*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_MASK RTL8367B_SWC0_MAX_LENGTH(0x3) +#define RTL8367B_SWC0_MAX_LENGTH_1522 RTL8367B_SWC0_MAX_LENGTH(0) +#define RTL8367B_SWC0_MAX_LENGTH_1536 RTL8367B_SWC0_MAX_LENGTH(1) +#define RTL8367B_SWC0_MAX_LENGTH_1552 RTL8367B_SWC0_MAX_LENGTH(2) +#define RTL8367B_SWC0_MAX_LENGTH_16000 RTL8367B_SWC0_MAX_LENGTH(3) + +#define RTL8367B_CHIP_NUMBER_REG 0x1300/*GOOD*/ + +#define RTL8367B_CHIP_VER_REG 0x1301/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_SHIFT 12/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_SHIFT 8/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_SHIFT 4/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_SHIFT 0/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_MASK 0x1/*GOOD*/ + +#define RTL8367B_CHIP_MODE_REG 0x1302 +#define RTL8367B_CHIP_MODE_MASK 0x7 + +#define RTL8367B_CHIP_DEBUG0_REG 0x1303 +#define RTL8367B_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367B_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367B_DIS_REG 0x1305 +#define RTL8367B_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367B_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367B_DIS_RGMII_MASK 0x7 + +#define RTL8367B_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367B_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367B_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367B_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367B_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367B_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367B_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367B_DI_FORCE_MODE BIT(12) +#define RTL8367B_DI_FORCE_NWAY BIT(7) +#define RTL8367B_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367B_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367B_DI_FORCE_LINK BIT(4) +#define RTL8367B_DI_FORCE_DUPLEX BIT(2) +#define RTL8367B_DI_FORCE_SPEED_MASK 3 +#define RTL8367B_DI_FORCE_SPEED_10 0 +#define RTL8367B_DI_FORCE_SPEED_100 1 +#define RTL8367B_DI_FORCE_SPEED_1000 2 + +#define RTL8367B_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367B_CHIP_RESET_REG 0x1322 /*GOOD*/ +#define RTL8367B_CHIP_RESET_SW BIT(1) /*GOOD*/ +#define RTL8367B_CHIP_RESET_HW BIT(0) /*GOOD*/ + +#define RTL8367B_PORT_STATUS_REG(_p) (0x1352 + (_p)) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_1000_SPI BIT(11) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_100_SPI BIT(10)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY_FAULT BIT(9)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK_MASTER BIT(8)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY BIT(7)/*GOOD*/ +#define RTL8367B_PORT_STATUS_TXPAUSE BIT(6)/*GOOD*/ +#define RTL8367B_PORT_STATUS_RXPAUSE BIT(5)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK BIT(4)/*GOOD*/ +#define RTL8367B_PORT_STATUS_DUPLEX BIT(2)/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_MASK 0x0003/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_10 0/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_100 1/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_1000 2/*GOOD*/ + +#define RTL8367B_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367B_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367B_IA_CTRL_REG 0x1f00 +#define RTL8367B_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367B_IA_CTRL_RW_READ RTL8367B_IA_CTRL_RW(0) +#define RTL8367B_IA_CTRL_RW_WRITE RTL8367B_IA_CTRL_RW(1) +#define RTL8367B_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367B_IA_STATUS_REG 0x1f01 +#define RTL8367B_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367B_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367B_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367B_IA_ADDRESS_REG 0x1f02 +#define RTL8367B_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367B_IA_READ_DATA_REG 0x1f04 + +#define RTL8367B_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367B_NUM_MIB_COUNTERS 58 + +#define RTL8367B_CPU_PORT_NUM 5 +#define RTL8367B_NUM_PORTS 8 +#define RTL8367B_NUM_VLANS 32 +#define RTL8367B_NUM_VIDS 4096 +#define RTL8367B_PRIORITYMAX 7 +#define RTL8367B_FIDMAX 7 + +#define RTL8367B_PORT_0 BIT(0) +#define RTL8367B_PORT_1 BIT(1) +#define RTL8367B_PORT_2 BIT(2) +#define RTL8367B_PORT_3 BIT(3) +#define RTL8367B_PORT_4 BIT(4) +#define RTL8367B_PORT_E0 BIT(5) /* External port 0 */ +#define RTL8367B_PORT_E1 BIT(6) /* External port 1 */ +#define RTL8367B_PORT_E2 BIT(7) /* External port 2 */ + +#define RTL8367B_PORTS_ALL \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \ + RTL8367B_PORT_E1 | RTL8367B_PORT_E2) + +#define RTL8367B_PORTS_ALL_BUT_CPU \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 | \ + RTL8367B_PORT_E2) + +struct rtl8367b_initval { + u16 reg; + u16 val; +}; + +#define RTL8367B_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367B_MIB_TXB_ID 28 /* IfOutOctets */ + +static struct rtl8366_mib_counter +rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = { + {0, 0, 4, "ifInOctets" }, + {0, 4, 2, "dot3StatsFCSErrors" }, + {0, 6, 2, "dot3StatsSymbolErrors" }, + {0, 8, 2, "dot3InPauseFrames" }, + {0, 10, 2, "dot3ControlInUnknownOpcodes" }, + {0, 12, 2, "etherStatsFragments" }, + {0, 14, 2, "etherStatsJabbers" }, + {0, 16, 2, "ifInUcastPkts" }, + {0, 18, 2, "etherStatsDropEvents" }, + {0, 20, 2, "ifInMulticastPkts" }, + {0, 22, 2, "ifInBroadcastPkts" }, + {0, 24, 2, "inMldChecksumError" }, + {0, 26, 2, "inIgmpChecksumError" }, + {0, 28, 2, "inMldSpecificQuery" }, + {0, 30, 2, "inMldGeneralQuery" }, + {0, 32, 2, "inIgmpSpecificQuery" }, + {0, 34, 2, "inIgmpGeneralQuery" }, + {0, 36, 2, "inMldLeaves" }, + {0, 38, 2, "inIgmpLeaves" }, + + {0, 40, 4, "etherStatsOctets" }, + {0, 44, 2, "etherStatsUnderSizePkts" }, + {0, 46, 2, "etherOversizeStats" }, + {0, 48, 2, "etherStatsPkts64Octets" }, + {0, 50, 2, "etherStatsPkts65to127Octets" }, + {0, 52, 2, "etherStatsPkts128to255Octets" }, + {0, 54, 2, "etherStatsPkts256to511Octets" }, + {0, 56, 2, "etherStatsPkts512to1023Octets" }, + {0, 58, 2, "etherStatsPkts1024to1518Octets" }, + + {0, 60, 4, "ifOutOctets" }, + {0, 64, 2, "dot3StatsSingleCollisionFrames" }, + {0, 66, 2, "dot3StatMultipleCollisionFrames" }, + {0, 68, 2, "dot3sDeferredTransmissions" }, + {0, 70, 2, "dot3StatsLateCollisions" }, + {0, 72, 2, "etherStatsCollisions" }, + {0, 74, 2, "dot3StatsExcessiveCollisions" }, + {0, 76, 2, "dot3OutPauseFrames" }, + {0, 78, 2, "ifOutDiscards" }, + {0, 80, 2, "dot1dTpPortInDiscards" }, + {0, 82, 2, "ifOutUcastPkts" }, + {0, 84, 2, "ifOutMulticastPkts" }, + {0, 86, 2, "ifOutBroadcastPkts" }, + {0, 88, 2, "outOampduPkts" }, + {0, 90, 2, "inOampduPkts" }, + {0, 92, 2, "inIgmpJoinsSuccess" }, + {0, 94, 2, "inIgmpJoinsFail" }, + {0, 96, 2, "inMldJoinsSuccess" }, + {0, 98, 2, "inMldJoinsFail" }, + {0, 100, 2, "inReportSuppressionDrop" }, + {0, 102, 2, "inLeaveSuppressionDrop" }, + {0, 104, 2, "outIgmpReports" }, + {0, 106, 2, "outIgmpLeaves" }, + {0, 108, 2, "outIgmpGeneralQuery" }, + {0, 110, 2, "outIgmpSpecificQuery" }, + {0, 112, 2, "outMldReports" }, + {0, 114, 2, "outMldLeaves" }, + {0, 116, 2, "outMldGeneralQuery" }, + {0, 118, 2, "outMldSpecificQuery" }, + {0, 120, 2, "inKnownMulticastPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14}, + {0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002}, + {0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000}, + {0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000}, + {0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000}, + {0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013}, + {0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, + {0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02}, + {0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007}, + {0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044}, + {0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1}, + {0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026}, + {0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000}, + {0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E}, + {0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00}, + {0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01}, + {0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02}, + {0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03}, + {0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04}, + {0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05}, + {0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06}, + {0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00}, + {0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000}, + {0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015}, + {0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E}, + {0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000}, + {0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E}, + {0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1}, + {0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E}, + {0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0}, + {0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0}, + {0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0}, + {0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0}, + {0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0}, + {0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0}, + {0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0}, + {0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2}, + {0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4}, + {0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4}, + {0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA}, + {0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA}, + {0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85}, + {0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B}, + {0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A}, + {0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A}, + {0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B}, + {0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF}, + {0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8}, + {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620}, + {0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225}, + {0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302}, + {0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1}, + {0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282}, + {0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6}, + {0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4}, + {0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305}, + {0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E}, + {0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E}, + {0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25}, + {0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B}, + {0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B}, + {0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4}, + {0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23}, + {0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359}, + {0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC}, + {0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29}, + {0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1}, + {0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29}, + {0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE}, + {0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0}, + {0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, + {0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0}, + {0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84}, + {0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72}, + {0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC}, + {0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, + {0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1}, + {0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B}, + {0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7}, + {0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC}, + {0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602}, + {0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02}, + {0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602}, + {0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE}, + {0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD}, + {0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22}, + {0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC}, + {0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F}, + {0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13}, + {0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87}, + {0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0}, + {0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0}, + {0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4}, + {0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12}, + {0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12}, + {0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146}, + {0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0}, + {0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F}, + {0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21}, + {0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF}, + {0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5}, + {0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10}, + {0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459}, + {0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F}, + {0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96}, + {0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF}, + {0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0}, + {0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F}, + {0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159}, + {0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C}, + {0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302}, + {0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34}, + {0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0}, + {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D}, + {0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A}, + {0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34}, + {0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34}, + {0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0}, + {0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D}, + {0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF}, + {0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498}, + {0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03}, + {0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207}, + {0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD}, + {0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31}, + {0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31}, + {0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284}, + {0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02}, + {0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4}, + {0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0}, + {0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1}, + {0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1}, + {0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, + {0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A}, + {0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0}, + {0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A}, + {0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C}, + {0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02}, + {0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA}, + {0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11}, + {0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04}, + {0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE}, + {0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE}, + {0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE}, + {0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34}, + {0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, + {0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, + {0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0}, + {0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20}, + {0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21}, + {0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE}, + {0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284}, + {0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8}, + {0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402}, + {0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285}, + {0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B}, + {0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D}, + {0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B}, + {0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8}, + {0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1}, + {0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5}, + {0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021}, + {0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000}, + {0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212}, + {0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26}, + {0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8}, + {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22}, + {0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464}, + {0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480}, + {0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142}, + {0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000}, + {0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010}, + {0x13EB, 0x11BB} +}; + +static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA}, + {0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078}, + {0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00}, + {0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000}, + {0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000}, + {0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000}, + {0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B}, + {0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00}, + {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100}, + {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0}, + {0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1}, + {0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE}, + {0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1}, + {0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD}, + {0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20}, + {0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A}, + {0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E}, + {0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5}, + {0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2}, + {0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC}, + {0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE}, + {0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, + {0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685}, + {0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85}, + {0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC}, + {0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140}, + {0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E}, + {0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22}, + {0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340}, + {0x133E, 0x000E}, {0x133F, 0x0010}, +}; + +static int rtl8367b_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367b_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367b_init_regs(struct rtl8366_smi *smi) +{ + const struct rtl8367b_initval *initvals; + u32 chip_ver; + u32 rlvid; + int count; + int err; + + REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL); + REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + + rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) & + RTL8367B_CHIP_VER_RLVID_MASK; + + switch (rlvid) { + case 0: + initvals = rtl8367r_vb_initvals_0; + count = ARRAY_SIZE(rtl8367r_vb_initvals_0); + break; + + case 1: + initvals = rtl8367r_vb_initvals_1; + count = ARRAY_SIZE(rtl8367r_vb_initvals_1); + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + return -ENODEV; + } + + /* TODO: disable RLTP */ + + return rtl8367b_write_initvals(smi, initvals, count); +} + +static int rtl8367b_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW); + msleep(RTL8367B_RESET_DELAY); + + do { + REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data); + if (!(data & RTL8367B_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG, + RTL8367B_CHIP_DEBUG0_DUMMY0(id), + RTL8367B_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367B_DIS_REG, + RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id), + mode << RTL8367B_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_DI_FORCE_MODE | + RTL8367B_DI_FORCE_NWAY | + RTL8367B_DI_FORCE_TXPAUSE | + RTL8367B_DI_FORCE_RXPAUSE | + RTL8367B_DI_FORCE_LINK | + RTL8367B_DI_FORCE_DUPLEX | + RTL8367B_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK | + (RTL8367B_EXT_RGMXF_TXDELAY_MASK << + RTL8367B_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367b_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367b_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367b_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367b_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367b_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367b_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK, + RTL8367B_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367B_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367B_PORT_MISC_CFG_REG(i), + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT, + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT); + + return 0; +} + +static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367B_NUM_PORTS || + counter >= RTL8367B_NUM_MIB_COUNTERS) + return -EINVAL; + + mib = &rtl8367b_mib_counters[counter]; + addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data); + + if (data & RTL8367B_MIB_CTRL0_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367B_MIB_CTRL0_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367B_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) & + RTL8367B_TA_VLAN0_MEMBER_MASK; + vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) & + RTL8367B_TA_VLAN0_UNTAG_MASK; + vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) & + RTL8367B_TA_VLAN1_FID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + if (vlan4k->vid >= RTL8367B_NUM_VIDS || + vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK || + vlan4k->untag > RTL8367B_UNTAG_MASK || + vlan4k->fid > RTL8367B_FIDMAX) + return -EINVAL; + + memset(data, 0, sizeof(data)); + + data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) << + RTL8367B_TA_VLAN0_MEMBER_SHIFT; + data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) << + RTL8367B_TA_VLAN0_UNTAG_SHIFT; + data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) << + RTL8367B_TA_VLAN1_FID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, + vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) & + RTL8367B_VLAN_MC0_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) & + RTL8367B_VLAN_MC1_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) & + RTL8367B_VLAN_MC3_EVID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + if (index >= RTL8367B_NUM_VLANS || + vlanmc->vid >= RTL8367B_NUM_VIDS || + vlanmc->priority > RTL8367B_PRIORITYMAX || + vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK || + vlanmc->untag > RTL8367B_UNTAG_MASK || + vlanmc->fid > RTL8367B_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) << + RTL8367B_VLAN_MC0_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) << + RTL8367B_VLAN_MC1_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) << + RTL8367B_VLAN_MC3_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367B_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), + RTL8367B_VLAN_PVID_CTRL_MASK << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367B_VLAN_PVID_CTRL_MASK) << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG, + RTL8367B_VLAN_CTRL_ENABLE, + (enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367B_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367B_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port), + (enable) ? RTL8367B_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367b_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0, + RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK); +} + +static int rtl8367b_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367B_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY); + + speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367b_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data); + val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >> + RTL8367B_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367b_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367B_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367B_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367B_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367B_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG, + RTL8367B_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0, + RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367b_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367b_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367b_sw_set_max_length, + .get = rtl8367b_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367b_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367b_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367b_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, +}; + +static const struct switch_dev_ops rtl8367b_sw_ops = { + .attr_global = { + .attr = rtl8367b_globals, + .n_attr = ARRAY_SIZE(rtl8367b_globals), + }, + .attr_port = { + .attr = rtl8367b_port, + .n_attr = ARRAY_SIZE(rtl8367b_port), + }, + .attr_vlan = { + .attr = rtl8367b_vlan, + .n_attr = ARRAY_SIZE(rtl8367b_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367b_sw_get_port_link, + .get_port_stats = rtl8367b_sw_get_port_stats, +}; + +static int rtl8367b_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367B"; + dev->cpu_port = RTL8367B_CPU_PORT_NUM; + dev->ports = RTL8367B_NUM_PORTS; + dev->vlans = RTL8367B_NUM_VIDS; + dev->ops = &rtl8367b_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367b_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367b_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367b_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367b_detect(struct rtl8366_smi *smi) +{ + const char *chip_name; + u32 chip_num; + u32 chip_ver; + u32 chip_mode; + int ret; + + /* TODO: improve chip detection */ + rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG, + RTL8367B_RTL_MAGIC_ID_VAL); + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip number"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip version"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip mode"); + return ret; + } + + switch (chip_ver) { + case 0x1000: + chip_name = "8367RB"; + break; + case 0x1010: + chip_name = "8367R-VB"; + break; + default: + dev_err(smi->parent, + "unknown chip num:%04x ver:%04x, mode:%04x\n", + chip_num, chip_ver, chip_mode); + return -ENODEV; + } + + dev_info(smi->parent, "RTL%s chip found\n", chip_name); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367b_smi_ops = { + .detect = rtl8367b_detect, + .reset_chip = rtl8367b_reset_chip, + .setup = rtl8367b_setup, + + .mii_read = rtl8367b_mii_read, + .mii_write = rtl8367b_mii_write, + + .get_vlan_mc = rtl8367b_get_vlan_mc, + .set_vlan_mc = rtl8367b_set_vlan_mc, + .get_vlan_4k = rtl8367b_get_vlan_4k, + .set_vlan_4k = rtl8367b_set_vlan_4k, + .get_mc_index = rtl8367b_get_mc_index, + .set_mc_index = rtl8367b_set_mc_index, + .get_mib_counter = rtl8367b_get_mib_counter, + .is_vlan_valid = rtl8367b_is_vlan_valid, + .enable_vlan = rtl8367b_enable_vlan, + .enable_vlan4k = rtl8367b_enable_vlan4k, + .enable_port = rtl8367b_enable_port, +}; + +static int rtl8367b_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367b_smi_ops; + smi->cpu_port = RTL8367B_CPU_PORT_NUM; + smi->num_ports = RTL8367B_NUM_PORTS; + smi->num_vlan_mc = RTL8367B_NUM_VLANS; + smi->mib_counters = rtl8367b_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367b_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367b_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367b_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367b_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367b_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367b_match[] = { + { .compatible = "realtek,rtl8367b" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367b_match); +#endif + +static struct platform_driver rtl8367b_driver = { + .driver = { + .name = RTL8367B_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367b_match), +#endif + }, + .probe = rtl8367b_probe, + .remove = rtl8367b_remove, + .shutdown = rtl8367b_shutdown, +}; + +module_platform_driver(rtl8367b_driver); + +MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME); + diff --git a/target/linux/generic/files-4.14/drivers/net/phy/swconfig.c b/target/linux/generic/files-4.14/drivers/net/phy/swconfig.c new file mode 100644 index 0000000000..e8a684742c --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/swconfig.c @@ -0,0 +1,1256 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWCONFIG_DEVNAME "switch%d" + +#include "swconfig_leds.c" + +MODULE_AUTHOR("Felix Fietkau "); +MODULE_LICENSE("GPL"); + +static int swdev_id; +static struct list_head swdevs; +static DEFINE_MUTEX(swdevs_lock); +struct swconfig_callback; + +struct swconfig_callback { + struct sk_buff *msg; + struct genlmsghdr *hdr; + struct genl_info *info; + int cmd; + + /* callback for filling in the message data */ + int (*fill)(struct swconfig_callback *cb, void *arg); + + /* callback for closing the message before sending it */ + int (*close)(struct swconfig_callback *cb, void *arg); + + struct nlattr *nest[4]; + int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + int ret; + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + if (!dev->ops->get_vlan_ports) + return -EOPNOTSUPP; + + ret = dev->ops->get_vlan_ports(dev, val); + return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port *ports = val->value.ports; + const struct switch_dev_ops *ops = dev->ops; + int i; + + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + /* validate ports */ + if (val->len > dev->ports) + return -EINVAL; + + if (!ops->set_vlan_ports) + return -EOPNOTSUPP; + + for (i = 0; i < val->len; i++) { + if (ports[i].id >= dev->ports) + return -EINVAL; + + if (ops->set_port_pvid && + !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + ops->set_port_pvid(dev, ports[i].id, val->port_vlan); + } + + return ops->set_vlan_ports(dev, val); +} + +static int +swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->set_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); +} + +static int +swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); +} + +static int +swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (!dev->ops->set_port_link) + return -EOPNOTSUPP; + + return dev->ops->set_port_link(dev, val->port_vlan, val->value.link); +} + +static int +swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port_link *link = val->value.link; + + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_link) + return -EOPNOTSUPP; + + memset(link, 0, sizeof(*link)); + return dev->ops->get_port_link(dev, val->port_vlan, link); +} + +static int +swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->apply_config) + return 0; + + return dev->ops->apply_config(dev); +} + +static int +swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->reset_switch) + return 0; + + return dev->ops->reset_switch(dev); +} + +enum global_defaults { + GLOBAL_APPLY, + GLOBAL_RESET, +}; + +enum vlan_defaults { + VLAN_PORTS, +}; + +enum port_defaults { + PORT_PVID, + PORT_LINK, +}; + +static struct switch_attr default_global[] = { + [GLOBAL_APPLY] = { + .type = SWITCH_TYPE_NOVAL, + .name = "apply", + .description = "Activate changes in the hardware", + .set = swconfig_apply_config, + }, + [GLOBAL_RESET] = { + .type = SWITCH_TYPE_NOVAL, + .name = "reset", + .description = "Reset the switch", + .set = swconfig_reset_switch, + } +}; + +static struct switch_attr default_port[] = { + [PORT_PVID] = { + .type = SWITCH_TYPE_INT, + .name = "pvid", + .description = "Primary VLAN ID", + .set = swconfig_set_pvid, + .get = swconfig_get_pvid, + }, + [PORT_LINK] = { + .type = SWITCH_TYPE_LINK, + .name = "link", + .description = "Get port link information", + .set = swconfig_set_link, + .get = swconfig_get_link, + } +}; + +static struct switch_attr default_vlan[] = { + [VLAN_PORTS] = { + .type = SWITCH_TYPE_PORTS, + .name = "ports", + .description = "VLAN port mapping", + .set = swconfig_set_vlan_ports, + .get = swconfig_get_vlan_ports, + }, +}; + +static const struct switch_attr * +swconfig_find_attr_by_name(const struct switch_attrlist *alist, + const char *name) +{ + int i; + + for (i = 0; i < alist->n_attr; i++) + if (strcmp(name, alist->attr[i].name) == 0) + return &alist->attr[i]; + + return NULL; +} + +static void swconfig_defaults_init(struct switch_dev *dev) +{ + const struct switch_dev_ops *ops = dev->ops; + + dev->def_global = 0; + dev->def_vlan = 0; + dev->def_port = 0; + + if (ops->get_vlan_ports || ops->set_vlan_ports) + set_bit(VLAN_PORTS, &dev->def_vlan); + + if (ops->get_port_pvid || ops->set_port_pvid) + set_bit(PORT_PVID, &dev->def_port); + + if (ops->get_port_link && + !swconfig_find_attr_by_name(&ops->attr_port, "link")) + set_bit(PORT_LINK, &dev->def_port); + + /* always present, can be no-op */ + set_bit(GLOBAL_APPLY, &dev->def_global); + set_bit(GLOBAL_RESET, &dev->def_global); +} + + +static struct genl_family switch_fam; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { + [SWITCH_ATTR_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, + [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, + [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { + [SWITCH_PORT_ID] = { .type = NLA_U32 }, + [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = { + [SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG }, + [SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG }, + [SWITCH_LINK_SPEED] = { .type = NLA_U32 }, +}; + +static inline void +swconfig_lock(void) +{ + mutex_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ + mutex_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ + struct switch_dev *dev = NULL; + struct switch_dev *p; + int id; + + if (!info->attrs[SWITCH_ATTR_ID]) + goto done; + + id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); + swconfig_lock(); + list_for_each_entry(p, &swdevs, dev_list) { + if (id != p->id) + continue; + + dev = p; + break; + } + if (dev) + mutex_lock(&dev->sw_mutex); + else + pr_debug("device %d not found\n", id); + swconfig_unlock(); +done: + return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ + mutex_unlock(&dev->sw_mutex); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ + struct switch_attr *op = arg; + struct genl_info *info = cb->info; + struct sk_buff *msg = cb->msg; + int id = cb->args[0]; + void *hdr; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) + goto nla_put_failure; + if (op->description) + if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, + op->description)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ + struct genl_info *info = cb->info; + int restart = 0; + int err; + + do { + if (!cb->msg) { + cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (cb->msg == NULL) + goto error; + } + + if (!(cb->fill(cb, arg) < 0)) + break; + + /* fill failed, check if this was already the second attempt */ + if (restart) + goto error; + + /* try again in a new message, send the current one */ + restart = 1; + if (cb->close) { + if (cb->close(cb, arg) < 0) + goto error; + } + err = genlmsg_reply(cb->msg, info); + cb->msg = NULL; + if (err < 0) + goto error; + + } while (restart); + + return 0; + +error: + if (cb->msg) + nlmsg_free(cb->msg); + return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + struct switch_dev *dev; + struct swconfig_callback cb; + int err = -EINVAL; + int i; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + switch (hdr->cmd) { + case SWITCH_CMD_LIST_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_LIST_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + break; + case SWITCH_CMD_LIST_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + break; + default: + WARN_ON(1); + goto out; + } + + memset(&cb, 0, sizeof(cb)); + cb.info = info; + cb.fill = swconfig_dump_attr; + for (i = 0; i < alist->n_attr; i++) { + if (alist->attr[i].disabled) + continue; + cb.args[0] = i; + err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); + if (err < 0) + goto error; + } + + /* defaults */ + for (i = 0; i < n_def; i++) { + if (!test_bit(i, def_active)) + continue; + cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; + err = swconfig_send_multipart(&cb, (void *) &def_list[i]); + if (err < 0) + goto error; + } + swconfig_put_dev(dev); + + if (!cb.msg) + return 0; + + return genlmsg_reply(cb.msg, info); + +error: + if (cb.msg) + nlmsg_free(cb.msg); +out: + swconfig_put_dev(dev); + return err; +} + +static const struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, + struct switch_val *val) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + const struct switch_attr *attr = NULL; + unsigned int attr_id; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + if (!info->attrs[SWITCH_ATTR_OP_ID]) + goto done; + + switch (hdr->cmd) { + case SWITCH_CMD_SET_GLOBAL: + case SWITCH_CMD_GET_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_SET_VLAN: + case SWITCH_CMD_GET_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + if (!info->attrs[SWITCH_ATTR_OP_VLAN]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); + if (val->port_vlan >= dev->vlans) + goto done; + break; + case SWITCH_CMD_SET_PORT: + case SWITCH_CMD_GET_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + if (!info->attrs[SWITCH_ATTR_OP_PORT]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); + if (val->port_vlan >= dev->ports) + goto done; + break; + default: + WARN_ON(1); + goto done; + } + + if (!alist) + goto done; + + attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); + if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { + attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; + if (attr_id >= n_def) + goto done; + if (!test_bit(attr_id, def_active)) + goto done; + attr = &def_list[attr_id]; + } else { + if (attr_id >= alist->n_attr) + goto done; + attr = &alist->attr[attr_id]; + } + + if (attr->disabled) + attr = NULL; + +done: + if (!attr) + pr_debug("attribute lookup failed\n"); + val->attr = attr; + return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, + struct switch_val *val, int max) +{ + struct nlattr *nla; + int rem; + + val->len = 0; + nla_for_each_nested(nla, head, rem) { + struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; + struct switch_port *port; + + if (val->len >= max) + return -EINVAL; + + port = &val->value.ports[val->len]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy)) +#else + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy, NULL)) +#endif + return -EINVAL; + + if (!tb[SWITCH_PORT_ID]) + return -EINVAL; + + port->id = nla_get_u32(tb[SWITCH_PORT_ID]); + if (tb[SWITCH_PORT_FLAG_TAGGED]) + port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla, + struct switch_port_link *link) +{ + struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy)) +#else + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL)) +#endif + return -EINVAL; + + link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX]; + link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG]; + link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]); + + return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ + const struct switch_attr *attr; + struct switch_dev *dev; + struct switch_val val; + int err = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->set) + goto error; + + val.attr = attr; + switch (attr->type) { + case SWITCH_TYPE_NOVAL: + break; + case SWITCH_TYPE_INT: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) + goto error; + val.value.i = + nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); + break; + case SWITCH_TYPE_STRING: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) + goto error; + val.value.s = + nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); + break; + case SWITCH_TYPE_PORTS: + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + + /* TODO: implement multipart? */ + if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { + err = swconfig_parse_ports(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], + &val, dev->ports); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + case SWITCH_TYPE_LINK: + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + + if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) { + err = swconfig_parse_link(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_LINK], + val.value.link); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + default: + goto error; + } + + err = attr->set(dev, attr, &val); +error: + swconfig_put_dev(dev); + return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ + if (cb->nest[0]) + nla_nest_end(cb->msg, cb->nest[0]); + return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ + const struct switch_port *port = arg; + struct nlattr *p = NULL; + + if (!cb->nest[0]) { + cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); + if (!cb->nest[0]) + return -1; + } + + p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); + if (!p) + goto error; + + if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) + goto nla_put_failure; + if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) + goto nla_put_failure; + } + + nla_nest_end(cb->msg, p); + return 0; + +nla_put_failure: + nla_nest_cancel(cb->msg, p); +error: + nla_nest_cancel(cb->msg, cb->nest[0]); + return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, + const struct switch_val *val) +{ + struct swconfig_callback cb; + int err = 0; + int i; + + if (!val->value.ports) + return -EINVAL; + + memset(&cb, 0, sizeof(cb)); + cb.cmd = attr; + cb.msg = *msg; + cb.info = info; + cb.fill = swconfig_send_port; + cb.close = swconfig_close_portlist; + + cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); + for (i = 0; i < val->len; i++) { + err = swconfig_send_multipart(&cb, &val->value.ports[i]); + if (err) + goto done; + } + err = val->len; + swconfig_close_portlist(&cb, NULL); + *msg = cb.msg; + +done: + return err; +} + +static int +swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr, + const struct switch_port_link *link) +{ + struct nlattr *p = NULL; + int err = 0; + + p = nla_nest_start(msg, attr); + if (link->link) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK)) + goto nla_put_failure; + } + if (link->duplex) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX)) + goto nla_put_failure; + } + if (link->aneg) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG)) + goto nla_put_failure; + } + if (link->tx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW)) + goto nla_put_failure; + } + if (link->rx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW)) + goto nla_put_failure; + } + if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed)) + goto nla_put_failure; + if (link->eee & ADVERTISED_100baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET)) + goto nla_put_failure; + } + if (link->eee & ADVERTISED_1000baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + + return err; + +nla_put_failure: + nla_nest_cancel(msg, p); + return -1; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attr *attr; + struct switch_dev *dev; + struct sk_buff *msg = NULL; + struct switch_val val; + int err = -EINVAL; + int cmd = hdr->cmd; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->get) + goto error; + + if (attr->type == SWITCH_TYPE_PORTS) { + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + } else if (attr->type == SWITCH_TYPE_LINK) { + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + } + + err = attr->get(dev, attr, &val); + if (err) + goto error; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + goto error; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + 0, cmd); + if (IS_ERR(hdr)) + goto nla_put_failure; + + switch (attr->type) { + case SWITCH_TYPE_INT: + if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) + goto nla_put_failure; + break; + case SWITCH_TYPE_STRING: + if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) + goto nla_put_failure; + break; + case SWITCH_TYPE_PORTS: + err = swconfig_send_ports(&msg, info, + SWITCH_ATTR_OP_VALUE_PORTS, &val); + if (err < 0) + goto nla_put_failure; + break; + case SWITCH_TYPE_LINK: + err = swconfig_send_link(msg, info, + SWITCH_ATTR_OP_VALUE_LINK, val.value.link); + if (err < 0) + goto nla_put_failure; + break; + default: + pr_debug("invalid type in attribute\n"); + err = -EINVAL; + goto nla_put_failure; + } + genlmsg_end(msg, hdr); + err = msg->len; + if (err < 0) + goto nla_put_failure; + + swconfig_put_dev(dev); + return genlmsg_reply(msg, info); + +nla_put_failure: + if (msg) + nlmsg_free(msg); +error: + swconfig_put_dev(dev); + if (!err) + err = -ENOMEM; + return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, + const struct switch_dev *dev) +{ + struct nlattr *p = NULL, *m = NULL; + void *hdr; + int i; + + hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, + SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) + goto nla_put_failure; + + m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); + if (!m) + goto nla_put_failure; + for (i = 0; i < dev->ports; i++) { + p = nla_nest_start(msg, SWITCH_ATTR_PORTS); + if (!p) + continue; + if (dev->portmap[i].s) { + if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, + dev->portmap[i].s)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, + dev->portmap[i].virt)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + } + nla_nest_end(msg, m); + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct switch_dev *dev; + int start = cb->args[0]; + int idx = 0; + + swconfig_lock(); + list_for_each_entry(dev, &swdevs, dev_list) { + if (++idx <= start) + continue; + if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + dev) < 0) + break; + } + swconfig_unlock(); + cb->args[0] = idx; + + return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ + return 0; +} + +static struct genl_ops swconfig_ops[] = { + { + .cmd = SWITCH_CMD_LIST_GLOBAL, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_VLAN, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_PORT, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_GLOBAL, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_VLAN, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_PORT, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_GLOBAL, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_VLAN, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_PORT, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_SWITCH, + .dumpit = swconfig_dump_switches, + .policy = switch_policy, + .done = swconfig_done, + } +}; + +static struct genl_family switch_fam = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + .id = GENL_ID_GENERATE, +#endif + .name = "switch", + .hdrsize = 0, + .version = 1, + .maxattr = SWITCH_ATTR_MAX, + .module = THIS_MODULE, + .ops = swconfig_ops, + .n_ops = ARRAY_SIZE(swconfig_ops), +}; + +#ifdef CONFIG_OF +void +of_switch_load_portmap(struct switch_dev *dev) +{ + struct device_node *port; + + if (!dev->of_node) + return; + + for_each_child_of_node(dev->of_node, port) { + const __be32 *prop; + const char *segment; + int size, phys; + + if (!of_device_is_compatible(port, "swconfig,port")) + continue; + + if (of_property_read_string(port, "swconfig,segment", &segment)) + continue; + + prop = of_get_property(port, "swconfig,portmap", &size); + if (!prop) + continue; + + if (size != (2 * sizeof(*prop))) { + pr_err("%s: failed to parse port mapping\n", + port->name); + continue; + } + + phys = be32_to_cpup(prop++); + if ((phys < 0) | (phys >= dev->ports)) { + pr_err("%s: physical port index out of range\n", + port->name); + continue; + } + + dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); + dev->portmap[phys].virt = be32_to_cpup(prop); + pr_debug("Found port: %s, physical: %d, virtual: %d\n", + segment, phys, dev->portmap[phys].virt); + } +} +#endif + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ + struct switch_dev *sdev; + const int max_switches = 8 * sizeof(unsigned long); + unsigned long in_use = 0; + int err; + int i; + + INIT_LIST_HEAD(&dev->dev_list); + if (netdev) { + dev->netdev = netdev; + if (!dev->alias) + dev->alias = netdev->name; + } + BUG_ON(!dev->alias); + + /* Make sure swdev_id doesn't overflow */ + if (swdev_id == INT_MAX) { + return -ENOMEM; + } + + if (dev->ports > 0) { + dev->portbuf = kzalloc(sizeof(struct switch_port) * + dev->ports, GFP_KERNEL); + if (!dev->portbuf) + return -ENOMEM; + dev->portmap = kzalloc(sizeof(struct switch_portmap) * + dev->ports, GFP_KERNEL); + if (!dev->portmap) { + kfree(dev->portbuf); + return -ENOMEM; + } + } + swconfig_defaults_init(dev); + mutex_init(&dev->sw_mutex); + swconfig_lock(); + dev->id = ++swdev_id; + + list_for_each_entry(sdev, &swdevs, dev_list) { + if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) + continue; + if (i < 0 || i > max_switches) + continue; + + set_bit(i, &in_use); + } + i = find_first_zero_bit(&in_use, max_switches); + + if (i == max_switches) { + swconfig_unlock(); + return -ENFILE; + } + +#ifdef CONFIG_OF + if (dev->ports) + of_switch_load_portmap(dev); +#endif + + /* fill device name */ + snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); + + list_add_tail(&dev->dev_list, &swdevs); + swconfig_unlock(); + + err = swconfig_create_led_trigger(dev); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ + swconfig_destroy_led_trigger(dev); + kfree(dev->portbuf); + mutex_lock(&dev->sw_mutex); + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + mutex_unlock(&dev->sw_mutex); +} +EXPORT_SYMBOL_GPL(unregister_switch); + +int +switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + if (WARN_ON(!dev->ops->phy_write16)) + return -ENOTSUPP; + + /* Generic implementation */ + if (link->aneg) { + dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); + dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + } else { + u16 bmcr = 0; + + if (link->duplex) + bmcr |= BMCR_FULLDPLX; + + switch (link->speed) { + case SWITCH_PORT_SPEED_10: + break; + case SWITCH_PORT_SPEED_100: + bmcr |= BMCR_SPEED100; + break; + case SWITCH_PORT_SPEED_1000: + bmcr |= BMCR_SPEED1000; + break; + default: + return -ENOTSUPP; + } + + dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); + } + + return 0; +} + +static int __init +swconfig_init(void) +{ + INIT_LIST_HEAD(&swdevs); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + return genl_register_family_with_ops(&switch_fam, swconfig_ops); +#else + return genl_register_family(&switch_fam); +#endif +} + +static void __exit +swconfig_exit(void) +{ + genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); diff --git a/target/linux/generic/files-4.14/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files-4.14/drivers/net/phy/swconfig_leds.c new file mode 100644 index 0000000000..91824b7cf6 --- /dev/null +++ b/target/linux/generic/files-4.14/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,556 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include +#include +#include +#include + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */ +#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \ + SWCONFIG_LED_PORT_SPEED_10 | \ + SWCONFIG_LED_PORT_SPEED_100 | \ + SWCONFIG_LED_PORT_SPEED_1000) + +#define SWCONFIG_LED_MODE_LINK 0x01 +#define SWCONFIG_LED_MODE_TX 0x02 +#define SWCONFIG_LED_MODE_RX 0x04 +#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) +#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \ + SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS]; + unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS]; + u8 link_speed[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; + u8 mode; + u8 speed_mask; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_set_brightness(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + int ret; + bool changed; + + ret = kstrtoul(buf, 0, &port_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + changed = (trig_data->port_mask != port_mask); + trig_data->port_mask = port_mask; + write_unlock(&trig_data->lock); + + if (changed) { + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + swconfig_trig_update_port_mask(led_cdev->trigger); + } + + return size; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u32 port_mask; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", port_mask); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +/* speed_mask file handler - display value */ +static ssize_t swconfig_trig_speed_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + + read_lock(&trig_data->lock); + speed_mask = trig_data->speed_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", speed_mask); + + return strlen(buf) + 1; +} + +/* speed_mask file handler - store value */ +static ssize_t swconfig_trig_speed_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + int ret; + + ret = kstrtou8(buf, 0, &speed_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL; + write_unlock(&trig_data->lock); + + return size; +} + +/* speed_mask special file */ +static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show, + swconfig_trig_speed_mask_store); + +static ssize_t swconfig_trig_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 mode; + + read_lock(&trig_data->lock); + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + if (mode == 0) { + strcpy(buf, "none\n"); + } else { + if (mode & SWCONFIG_LED_MODE_LINK) + strcat(buf, "link "); + if (mode & SWCONFIG_LED_MODE_TX) + strcat(buf, "tx "); + if (mode & SWCONFIG_LED_MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + return strlen(buf)+1; +} + +static ssize_t swconfig_trig_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode < 0) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= SWCONFIG_LED_MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= SWCONFIG_LED_MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= SWCONFIG_LED_MODE_LINK; + else + return -EINVAL; + } + + if (new_mode < 0) + return -EINVAL; + + write_lock(&trig_data->lock); + trig_data->mode = (u8)new_mode; + write_unlock(&trig_data->lock); + + return size; +} + +/* mode special file */ +static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show, + swconfig_trig_mode_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL; + trig_data->mode = SWCONFIG_LED_MODE_ALL; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + err = device_create_file(led_cdev->dev, &dev_attr_speed_mask); + if (err) + goto err_dev_free; + + err = device_create_file(led_cdev->dev, &dev_attr_mode); + if (err) + goto err_mode_free; + + return; + +err_mode_free: + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + +err_dev_free: + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + device_remove_file(led_cdev->dev, &dev_attr_mode); + kfree(trig_data); + } +} + +/* + * link off -> led off (can't be any other reason to turn it on) + * link on: + * mode link: led on by default only if speed matches, else off + * mode txrx: blink only if speed matches, else off + */ +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + u8 speed_mask, mode; + enum led_brightness led_base, led_blink; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + speed_mask = trig_data->speed_mask; + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */ + } + else { + unsigned long traffic; + int speedok; /* link speed flag */ + int i; + + led_base = LED_FULL; + led_blink = LED_OFF; + traffic = 0; + speedok = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) { + if (sw_trig->link_speed[i] & speed_mask) { + traffic += ((mode & SWCONFIG_LED_MODE_TX) ? + sw_trig->port_tx_traffic[i] : 0) + + ((mode & SWCONFIG_LED_MODE_RX) ? + sw_trig->port_rx_traffic[i] : 0); + speedok = 1; + } + } + } + + if (speedok) { + /* At least one port speed matches speed_mask */ + if (!(mode & SWCONFIG_LED_MODE_LINK)) { + led_base = LED_OFF; + led_blink = LED_FULL; + } + + if (trig_data->prev_brightness != led_base) + swconfig_trig_set_brightness(trig_data, + led_base); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, + led_blink); + } else if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + sw_trig->link_speed[i] = 0; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) { + link |= port_bit; + switch (port_link.speed) { + case SWITCH_PORT_SPEED_UNKNOWN: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_NA; + break; + case SWITCH_PORT_SPEED_10: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_1000; + break; + } + } + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_tx_traffic[i] = port_stats.tx_bytes; + sw_trig->port_rx_traffic[i] = port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files-4.14/include/linux/ar8216_platform.h b/target/linux/generic/files-4.14/include/linux/ar8216_platform.h new file mode 100644 index 0000000000..24bc442a26 --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/ar8216_platform.h @@ -0,0 +1,133 @@ +/* + * AR8216 switch driver platform data + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef AR8216_PLATFORM_H +#define AR8216_PLATFORM_H + +enum ar8327_pad_mode { + AR8327_PAD_NC = 0, + AR8327_PAD_MAC2MAC_MII, + AR8327_PAD_MAC2MAC_GMII, + AR8327_PAD_MAC_SGMII, + AR8327_PAD_MAC2PHY_MII, + AR8327_PAD_MAC2PHY_GMII, + AR8327_PAD_MAC_RGMII, + AR8327_PAD_PHY_GMII, + AR8327_PAD_PHY_RGMII, + AR8327_PAD_PHY_MII, +}; + +enum ar8327_clk_delay_sel { + AR8327_CLK_DELAY_SEL0 = 0, + AR8327_CLK_DELAY_SEL1, + AR8327_CLK_DELAY_SEL2, + AR8327_CLK_DELAY_SEL3, +}; + +struct ar8327_pad_cfg { + enum ar8327_pad_mode mode; + bool rxclk_sel; + bool txclk_sel; + bool pipe_rxclk_sel; + bool txclk_delay_en; + bool rxclk_delay_en; + bool sgmii_delay_en; + enum ar8327_clk_delay_sel txclk_delay_sel; + enum ar8327_clk_delay_sel rxclk_delay_sel; + bool mac06_exchange_dis; +}; + +enum ar8327_port_speed { + AR8327_PORT_SPEED_10 = 0, + AR8327_PORT_SPEED_100, + AR8327_PORT_SPEED_1000, +}; + +struct ar8327_port_cfg { + int force_link:1; + enum ar8327_port_speed speed; + int txpause:1; + int rxpause:1; + int duplex:1; +}; + +struct ar8327_sgmii_cfg { + u32 sgmii_ctrl; + bool serdes_aen; +}; + +struct ar8327_led_cfg { + u32 led_ctrl0; + u32 led_ctrl1; + u32 led_ctrl2; + u32 led_ctrl3; + bool open_drain; +}; + +enum ar8327_led_num { + AR8327_LED_PHY0_0 = 0, + AR8327_LED_PHY0_1, + AR8327_LED_PHY0_2, + AR8327_LED_PHY1_0, + AR8327_LED_PHY1_1, + AR8327_LED_PHY1_2, + AR8327_LED_PHY2_0, + AR8327_LED_PHY2_1, + AR8327_LED_PHY2_2, + AR8327_LED_PHY3_0, + AR8327_LED_PHY3_1, + AR8327_LED_PHY3_2, + AR8327_LED_PHY4_0, + AR8327_LED_PHY4_1, + AR8327_LED_PHY4_2, +}; + +enum ar8327_led_mode { + AR8327_LED_MODE_HW = 0, + AR8327_LED_MODE_SW, +}; + +struct ar8327_led_info { + const char *name; + const char *default_trigger; + bool active_low; + enum ar8327_led_num led_num; + enum ar8327_led_mode mode; +}; + +#define AR8327_LED_INFO(_led, _mode, _name) { \ + .name = (_name), \ + .led_num = AR8327_LED_ ## _led, \ + .mode = AR8327_LED_MODE_ ## _mode \ +} + +struct ar8327_platform_data { + struct ar8327_pad_cfg *pad0_cfg; + struct ar8327_pad_cfg *pad5_cfg; + struct ar8327_pad_cfg *pad6_cfg; + struct ar8327_sgmii_cfg *sgmii_cfg; + struct ar8327_port_cfg port0_cfg; + struct ar8327_port_cfg port6_cfg; + struct ar8327_led_cfg *led_cfg; + + int (*get_port_link)(unsigned port); + + unsigned num_leds; + const struct ar8327_led_info *leds; +}; + +#endif /* AR8216_PLATFORM_H */ + diff --git a/target/linux/generic/files-4.14/include/linux/ath5k_platform.h b/target/linux/generic/files-4.14/include/linux/ath5k_platform.h new file mode 100644 index 0000000000..ec85224528 --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/ath5k_platform.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * Copyright (c) 2010 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH5K_PLATFORM_H +#define _LINUX_ATH5K_PLATFORM_H + +#define ATH5K_PLAT_EEP_MAX_WORDS 2048 + +struct ath5k_platform_data { + u16 *eeprom_data; + u8 *macaddr; +}; + +#endif /* _LINUX_ATH5K_PLATFORM_H */ diff --git a/target/linux/generic/files-4.14/include/linux/ath9k_platform.h b/target/linux/generic/files-4.14/include/linux/ath9k_platform.h new file mode 100644 index 0000000000..f1f2ad419c --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/ath9k_platform.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH9K_PLATFORM_H +#define _LINUX_ATH9K_PLATFORM_H + +#define ATH9K_PLAT_EEP_MAX_WORDS 2048 + +struct ath9k_platform_data { + const char *eeprom_name; + + u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS]; + u8 *macaddr; + + int led_pin; + u32 gpio_mask; + u32 gpio_val; + + u32 bt_active_pin; + u32 bt_priority_pin; + u32 wlan_active_pin; + + bool endian_check; + bool is_clk_25mhz; + bool tx_gain_buffalo; + bool disable_2ghz; + bool disable_5ghz; + bool led_active_high; + + int (*get_mac_revision)(void); + int (*external_reset)(void); + + bool use_eeprom; + + int num_leds; + const struct gpio_led *leds; + + unsigned num_btns; + const struct gpio_keys_button *btns; + unsigned btn_poll_interval; + + bool ubnt_hsr; +}; + +#endif /* _LINUX_ATH9K_PLATFORM_H */ diff --git a/target/linux/generic/files-4.14/include/linux/myloader.h b/target/linux/generic/files-4.14/include/linux/myloader.h new file mode 100644 index 0000000000..d89e415fba --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/myloader.h @@ -0,0 +1,121 @@ +/* + * Compex's MyLoader specific definitions + * + * Copyright (C) 2006-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MYLOADER_H_ +#define _MYLOADER_H_ + +/* Myloader specific magic numbers */ +#define MYLO_MAGIC_SYS_PARAMS 0x20021107 +#define MYLO_MAGIC_PARTITIONS 0x20021103 +#define MYLO_MAGIC_BOARD_PARAMS 0x20021103 + +/* Vendor ID's (seems to be same as the PCI vendor ID's) */ +#define VENID_COMPEX 0x11F6 + +/* Devices based on the ADM5120 */ +#define DEVID_COMPEX_NP27G 0x0078 +#define DEVID_COMPEX_NP28G 0x044C +#define DEVID_COMPEX_NP28GHS 0x044E +#define DEVID_COMPEX_WP54Gv1C 0x0514 +#define DEVID_COMPEX_WP54G 0x0515 +#define DEVID_COMPEX_WP54AG 0x0546 +#define DEVID_COMPEX_WPP54AG 0x0550 +#define DEVID_COMPEX_WPP54G 0x0555 + +/* Devices based on the Atheros AR2317 */ +#define DEVID_COMPEX_NP25G 0x05E6 +#define DEVID_COMPEX_WPE53G 0x05DC + +/* Devices based on the Atheros AR71xx */ +#define DEVID_COMPEX_WP543 0x0640 +#define DEVID_COMPEX_WPE72 0x0672 + +/* Devices based on the IXP422 */ +#define DEVID_COMPEX_WP18 0x047E +#define DEVID_COMPEX_NP18A 0x0489 + +/* Other devices */ +#define DEVID_COMPEX_NP26G8M 0x03E8 +#define DEVID_COMPEX_NP26G16M 0x03E9 + +struct mylo_partition { + uint16_t flags; /* partition flags */ + uint16_t type; /* type of the partition */ + uint32_t addr; /* relative address of the partition from the + flash start */ + uint32_t size; /* size of the partition in bytes */ + uint32_t param; /* if this is the active partition, the + MyLoader load code to this address */ +}; + +#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition, + * MyLoader loads firmware from here */ +#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */ +#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */ +#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM + * before decompression */ +#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */ +#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */ + +#define PARTITION_TYPE_FREE 0 +#define PARTITION_TYPE_USED 1 + +#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the + partition table */ + +struct mylo_partition_table { + uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */ + uint32_t res0; /* unknown/unused */ + uint32_t res1; /* unknown/unused */ + uint32_t res2; /* unknown/unused */ + struct mylo_partition partitions[MYLO_MAX_PARTITIONS]; +}; + +struct mylo_partition_header { + uint32_t len; /* length of the partition data */ + uint32_t crc; /* CRC value of the partition data */ +}; + +struct mylo_system_params { + uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t mylo_ver; + uint16_t vid; /* Vendor ID */ + uint16_t did; /* Device ID */ + uint16_t svid; /* Sub Vendor ID */ + uint16_t sdid; /* Sub Device ID */ + uint32_t rev; /* device revision */ + uint32_t fwhi; + uint32_t fwlo; + uint32_t tftp_addr; + uint32_t prog_start; + uint32_t flash_size; /* size of boot FLASH in bytes */ + uint32_t dram_size; /* size of onboard RAM in bytes */ +}; + +struct mylo_eth_addr { + uint8_t mac[6]; + uint8_t csum[2]; +}; + +#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address + in the board parameters */ + +struct mylo_board_params { + uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t res2; + struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT]; +}; + +#endif /* _MYLOADER_H_*/ diff --git a/target/linux/generic/files-4.14/include/linux/platform_data/adm6996-gpio.h b/target/linux/generic/files-4.14/include/linux/platform_data/adm6996-gpio.h new file mode 100644 index 0000000000..d5af9bbf6e --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/platform_data/adm6996-gpio.h @@ -0,0 +1,29 @@ +/* + * ADM6996 GPIO platform data + * + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __PLATFORM_ADM6996_GPIO_H +#define __PLATFORM_ADM6996_GPIO_H + +#include + +enum adm6996_model { + ADM6996FC = 1, + ADM6996M = 2, + ADM6996L = 3, +}; + +struct adm6996_gpio_platform_data { + u8 eecs; + u8 eesk; + u8 eedi; + enum adm6996_model model; +}; + +#endif diff --git a/target/linux/generic/files-4.14/include/linux/platform_data/b53.h b/target/linux/generic/files-4.14/include/linux/platform_data/b53.h new file mode 100644 index 0000000000..78427417a1 --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/platform_data/b53.h @@ -0,0 +1,36 @@ +/* + * B53 platform data + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_H +#define __B53_H + +#include + +struct b53_platform_data { + u32 chip_id; + u16 enabled_ports; + + /* allow to specify an ethX alias */ + const char *alias; + + /* only used by MMAP'd driver */ + unsigned big_endian:1; + void __iomem *regs; +}; + +#endif diff --git a/target/linux/generic/files-4.14/include/linux/routerboot.h b/target/linux/generic/files-4.14/include/linux/routerboot.h new file mode 100644 index 0000000000..3cda858cf9 --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/routerboot.h @@ -0,0 +1,106 @@ +/* + * Mikrotik's RouterBOOT definitions + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _ROUTERBOOT_H +#define _ROUTERBOOT_H + +#define RB_MAC_SIZE 6 + +/* + * Magic numbers + */ +#define RB_MAGIC_HARD 0x64726148 /* "Hard" */ +#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */ +#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */ + +#define RB_ID_TERMINATOR 0 + +/* + * ID values for Hardware settings + */ +#define RB_ID_HARD_01 1 +#define RB_ID_HARD_02 2 +#define RB_ID_FLASH_INFO 3 +#define RB_ID_MAC_ADDRESS_PACK 4 +#define RB_ID_BOARD_NAME 5 +#define RB_ID_BIOS_VERSION 6 +#define RB_ID_HARD_07 7 +#define RB_ID_SDRAM_TIMINGS 8 +#define RB_ID_DEVICE_TIMINGS 9 +#define RB_ID_SOFTWARE_ID 10 +#define RB_ID_SERIAL_NUMBER 11 +#define RB_ID_HARD_12 12 +#define RB_ID_MEMORY_SIZE 13 +#define RB_ID_MAC_ADDRESS_COUNT 14 +#define RB_ID_HW_OPTIONS 21 +#define RB_ID_WLAN_DATA 22 + +/* + * ID values for Software settings + */ +#define RB_ID_UART_SPEED 1 +#define RB_ID_BOOT_DELAY 2 +#define RB_ID_BOOT_DEVICE 3 +#define RB_ID_BOOT_KEY 4 +#define RB_ID_CPU_MODE 5 +#define RB_ID_FW_VERSION 6 +#define RB_ID_SOFT_07 7 +#define RB_ID_SOFT_08 8 +#define RB_ID_BOOT_PROTOCOL 9 +#define RB_ID_SOFT_10 10 +#define RB_ID_SOFT_11 11 + +/* + * UART_SPEED values + */ +#define RB_UART_SPEED_115200 0 +#define RB_UART_SPEED_57600 1 +#define RB_UART_SPEED_38400 2 +#define RB_UART_SPEED_19200 3 +#define RB_UART_SPEED_9600 4 +#define RB_UART_SPEED_4800 5 +#define RB_UART_SPEED_2400 6 +#define RB_UART_SPEED_1200 7 + +/* + * BOOT_DELAY values + */ +#define RB_BOOT_DELAY_0SEC 0 +#define RB_BOOT_DELAY_1SEC 1 +#define RB_BOOT_DELAY_2SEC 2 + +/* + * BOOT_DEVICE values + */ +#define RB_BOOT_DEVICE_ETHER 0 +#define RB_BOOT_DEVICE_NANDETH 1 +#define RB_BOOT_DEVICE_ETHONCE 2 +#define RB_BOOT_DEVICE_NANDONLY 3 + +/* + * BOOT_KEY values + */ +#define RB_BOOT_KEY_ANY 0 +#define RB_BOOT_KEY_DEL 1 + +/* + * CPU_MODE values + */ +#define RB_CPU_MODE_POWERSAVE 0 +#define RB_CPU_MODE_REGULAR 1 + +/* + * BOOT_PROTOCOL values + */ +#define RB_BOOT_PROTOCOL_BOOTP 0 +#define RB_BOOT_PROTOCOL_DHCP 1 + +#endif /* _ROUTERBOOT_H */ diff --git a/target/linux/generic/files-4.14/include/linux/rt2x00_platform.h b/target/linux/generic/files-4.14/include/linux/rt2x00_platform.h new file mode 100644 index 0000000000..e10377e21b --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/rt2x00_platform.h @@ -0,0 +1,23 @@ +/* + * Platform data definition for the rt2x00 driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _RT2X00_PLATFORM_H +#define _RT2X00_PLATFORM_H + +struct rt2x00_platform_data { + char *eeprom_file_name; + const u8 *mac_address; + + int disable_2ghz; + int disable_5ghz; +}; + +#endif /* _RT2X00_PLATFORM_H */ diff --git a/target/linux/generic/files-4.14/include/linux/rtl8366.h b/target/linux/generic/files-4.14/include/linux/rtl8366.h new file mode 100644 index 0000000000..e3ce8f5361 --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/rtl8366.h @@ -0,0 +1,42 @@ +/* + * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_H +#define _RTL8366_H + +#define RTL8366_DRIVER_NAME "rtl8366" +#define RTL8366S_DRIVER_NAME "rtl8366s" +#define RTL8366RB_DRIVER_NAME "rtl8366rb" + +struct rtl8366_smi; + +enum rtl8366_type { + RTL8366_TYPE_UNKNOWN, + RTL8366_TYPE_S, + RTL8366_TYPE_RB, +}; + +struct rtl8366_initval { + unsigned reg; + u16 val; +}; + +struct rtl8366_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + + unsigned num_initvals; + struct rtl8366_initval *initvals; +}; + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata); + +#endif /* _RTL8366_H */ diff --git a/target/linux/generic/files-4.14/include/linux/rtl8367.h b/target/linux/generic/files-4.14/include/linux/rtl8367.h new file mode 100644 index 0000000000..855de6a5cc --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/rtl8367.h @@ -0,0 +1,60 @@ +/* + * Platform data definition for the Realtek RTL8367 ethernet switch driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8367_H +#define _RTL8367_H + +#define RTL8367_DRIVER_NAME "rtl8367" +#define RTL8367B_DRIVER_NAME "rtl8367b" + +enum rtl8367_port_speed { + RTL8367_PORT_SPEED_10 = 0, + RTL8367_PORT_SPEED_100, + RTL8367_PORT_SPEED_1000, +}; + +struct rtl8367_port_ability { + int force_mode; + int nway; + int txpause; + int rxpause; + int link; + int duplex; + enum rtl8367_port_speed speed; +}; + +enum rtl8367_extif_mode { + RTL8367_EXTIF_MODE_DISABLED = 0, + RTL8367_EXTIF_MODE_RGMII, + RTL8367_EXTIF_MODE_MII_MAC, + RTL8367_EXTIF_MODE_MII_PHY, + RTL8367_EXTIF_MODE_TMII_MAC, + RTL8367_EXTIF_MODE_TMII_PHY, + RTL8367_EXTIF_MODE_GMII, + RTL8367_EXTIF_MODE_RGMII_33V, +}; + +struct rtl8367_extif_config { + unsigned int txdelay; + unsigned int rxdelay; + enum rtl8367_extif_mode mode; + struct rtl8367_port_ability ability; +}; + +struct rtl8367_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(bool active); + + struct rtl8367_extif_config *extif0_cfg; + struct rtl8367_extif_config *extif1_cfg; +}; + +#endif /* _RTL8367_H */ diff --git a/target/linux/generic/files-4.14/include/linux/switch.h b/target/linux/generic/files-4.14/include/linux/switch.h new file mode 100644 index 0000000000..4e6238470d --- /dev/null +++ b/target/linux/generic/files-4.14/include/linux/switch.h @@ -0,0 +1,179 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _LINUX_SWITCH_H +#define _LINUX_SWITCH_H + +#include +#include + +struct switch_dev; +struct switch_op; +struct switch_val; +struct switch_attr; +struct switch_attrlist; +struct switch_led_trigger; + +int register_switch(struct switch_dev *dev, struct net_device *netdev); +void unregister_switch(struct switch_dev *dev); + +/** + * struct switch_attrlist - attribute list + * + * @n_attr: number of attributes + * @attr: pointer to the attributes array + */ +struct switch_attrlist { + int n_attr; + const struct switch_attr *attr; +}; + +enum switch_port_speed { + SWITCH_PORT_SPEED_UNKNOWN = 0, + SWITCH_PORT_SPEED_10 = 10, + SWITCH_PORT_SPEED_100 = 100, + SWITCH_PORT_SPEED_1000 = 1000, +}; + +struct switch_port_link { + bool link; + bool duplex; + bool aneg; + bool tx_flow; + bool rx_flow; + enum switch_port_speed speed; + /* in ethtool adv_t format */ + u32 eee; +}; + +struct switch_port_stats { + unsigned long long tx_bytes; + unsigned long long rx_bytes; +}; + +/** + * struct switch_dev_ops - switch driver operations + * + * @attr_global: global switch attribute list + * @attr_port: port attribute list + * @attr_vlan: vlan attribute list + * + * Callbacks: + * + * @get_vlan_ports: read the port list of a VLAN + * @set_vlan_ports: set the port list of a VLAN + * + * @get_port_pvid: get the primary VLAN ID of a port + * @set_port_pvid: set the primary VLAN ID of a port + * + * @apply_config: apply all changed settings to the switch + * @reset_switch: resetting the switch + */ +struct switch_dev_ops { + struct switch_attrlist attr_global, attr_port, attr_vlan; + + int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + + int (*get_port_pvid)(struct switch_dev *dev, int port, int *val); + int (*set_port_pvid)(struct switch_dev *dev, int port, int val); + + int (*apply_config)(struct switch_dev *dev); + int (*reset_switch)(struct switch_dev *dev); + + int (*get_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*set_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*get_port_stats)(struct switch_dev *dev, int port, + struct switch_port_stats *stats); + + int (*phy_read16)(struct switch_dev *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct switch_dev *dev, int addr, u8 reg, u16 value); +}; + +struct switch_dev { + struct device_node *of_node; + const struct switch_dev_ops *ops; + /* will be automatically filled */ + char devname[IFNAMSIZ]; + + const char *name; + /* NB: either alias or netdev must be set */ + const char *alias; + struct net_device *netdev; + + unsigned int ports; + unsigned int vlans; + unsigned int cpu_port; + + /* the following fields are internal for swconfig */ + unsigned int id; + struct list_head dev_list; + unsigned long def_global, def_port, def_vlan; + + struct mutex sw_mutex; + struct switch_port *portbuf; + struct switch_portmap *portmap; + struct switch_port_link linkbuf; + + char buf[128]; + +#ifdef CONFIG_SWCONFIG_LEDS + struct switch_led_trigger *led_trigger; +#endif +}; + +struct switch_port { + u32 id; + u32 flags; +}; + +struct switch_portmap { + u32 virt; + const char *s; +}; + +struct switch_val { + const struct switch_attr *attr; + unsigned int port_vlan; + unsigned int len; + union { + const char *s; + u32 i; + struct switch_port *ports; + struct switch_port_link *link; + } value; +}; + +struct switch_attr { + int disabled; + int type; + const char *name; + const char *description; + + int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + + /* for driver internal use */ + int id; + int ofs; + int max; +}; + +int switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link); + +#endif /* _LINUX_SWITCH_H */ diff --git a/target/linux/generic/files-4.14/include/uapi/linux/switch.h b/target/linux/generic/files-4.14/include/uapi/linux/switch.h new file mode 100644 index 0000000000..ea449653fa --- /dev/null +++ b/target/linux/generic/files-4.14/include/uapi/linux/switch.h @@ -0,0 +1,119 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _UAPI_LINUX_SWITCH_H +#define _UAPI_LINUX_SWITCH_H + +#include +#include +#include +#include +#ifndef __KERNEL__ +#include +#include +#include +#endif + +/* main attributes */ +enum { + SWITCH_ATTR_UNSPEC, + /* global */ + SWITCH_ATTR_TYPE, + /* device */ + SWITCH_ATTR_ID, + SWITCH_ATTR_DEV_NAME, + SWITCH_ATTR_ALIAS, + SWITCH_ATTR_NAME, + SWITCH_ATTR_VLANS, + SWITCH_ATTR_PORTS, + SWITCH_ATTR_PORTMAP, + SWITCH_ATTR_CPU_PORT, + /* attributes */ + SWITCH_ATTR_OP_ID, + SWITCH_ATTR_OP_TYPE, + SWITCH_ATTR_OP_NAME, + SWITCH_ATTR_OP_PORT, + SWITCH_ATTR_OP_VLAN, + SWITCH_ATTR_OP_VALUE_INT, + SWITCH_ATTR_OP_VALUE_STR, + SWITCH_ATTR_OP_VALUE_PORTS, + SWITCH_ATTR_OP_VALUE_LINK, + SWITCH_ATTR_OP_DESCRIPTION, + /* port lists */ + SWITCH_ATTR_PORT, + SWITCH_ATTR_MAX +}; + +enum { + /* port map */ + SWITCH_PORTMAP_PORTS, + SWITCH_PORTMAP_SEGMENT, + SWITCH_PORTMAP_VIRT, + SWITCH_PORTMAP_MAX +}; + +/* commands */ +enum { + SWITCH_CMD_UNSPEC, + SWITCH_CMD_GET_SWITCH, + SWITCH_CMD_NEW_ATTR, + SWITCH_CMD_LIST_GLOBAL, + SWITCH_CMD_GET_GLOBAL, + SWITCH_CMD_SET_GLOBAL, + SWITCH_CMD_LIST_PORT, + SWITCH_CMD_GET_PORT, + SWITCH_CMD_SET_PORT, + SWITCH_CMD_LIST_VLAN, + SWITCH_CMD_GET_VLAN, + SWITCH_CMD_SET_VLAN +}; + +/* data types */ +enum switch_val_type { + SWITCH_TYPE_UNSPEC, + SWITCH_TYPE_INT, + SWITCH_TYPE_STRING, + SWITCH_TYPE_PORTS, + SWITCH_TYPE_LINK, + SWITCH_TYPE_NOVAL, +}; + +/* port nested attributes */ +enum { + SWITCH_PORT_UNSPEC, + SWITCH_PORT_ID, + SWITCH_PORT_FLAG_TAGGED, + SWITCH_PORT_ATTR_MAX +}; + +/* link nested attributes */ +enum { + SWITCH_LINK_UNSPEC, + SWITCH_LINK_FLAG_LINK, + SWITCH_LINK_FLAG_DUPLEX, + SWITCH_LINK_FLAG_ANEG, + SWITCH_LINK_FLAG_TX_FLOW, + SWITCH_LINK_FLAG_RX_FLOW, + SWITCH_LINK_SPEED, + SWITCH_LINK_FLAG_EEE_100BASET, + SWITCH_LINK_FLAG_EEE_1000BASET, + SWITCH_LINK_ATTR_MAX, +}; + +#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000 + + +#endif /* _UAPI_LINUX_SWITCH_H */ diff --git a/target/linux/generic/files-4.9/Documentation/networking/adm6996.txt b/target/linux/generic/files-4.9/Documentation/networking/adm6996.txt new file mode 100644 index 0000000000..ab59f1df03 --- /dev/null +++ b/target/linux/generic/files-4.9/Documentation/networking/adm6996.txt @@ -0,0 +1,110 @@ +------- + +ADM6996FC / ADM6996M switch chip driver + + +1. General information + + This driver supports the FC and M models only. The ADM6996F and L are + completely different chips. + + Support for the FC model is extremely limited at the moment. There is no VLAN + support as of yet. The driver will not offer an swconfig interface for the FC + chip. + +1.1 VLAN IDs + + It is possible to define 16 different VLANs. Every VLAN has an identifier, its + VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the + swconfig based configuration is very straightforward. To define two VLANs with + IDs 4 and 5, you can invoke, for example: + + # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' + # swconfig dev ethX vlan 5 set ports '0t 1t 5t' + + The swconfig framework will automatically invoke 'port Y set pvid Z' for every + port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In + this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port + is the VLAN ID associated with untagged packets coming in on that port. + + But if you wish to use VLAN IDs outside the range 0-15, this automatic + behaviour of the swconfig framework becomes a problem. The 16 VLANs that + swconfig can configure on the ADM6996 also have a "vid" setting. By default, + this is the same as the number of the VLAN entry, to make the simple behaviour + above possible. To still support a VLAN with a VLAN ID higher than 15 + (presumably because you are in a network where such VLAN IDs are already in + use), you can change the "vid" setting of the VLAN to anything in the range + 0-1023. But suppose you did the following: + + # swconfig dev ethX vlan 0 set vid 998 + # swconfig dev ethX vlan 0 set ports '0 2 5t' + + Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid + 0'. But the "pvid" should be set to 998, so you are responsible for manually + fixing this! + +1.2 VLAN filtering + + The switch is configured to apply source port filtering. This means that + packets are only accepted when the port the packets came in on is a member of + the VLAN the packet should go to. + + Only membership of a VLAN is tested, it does not matter whether it is a tagged + or untagged membership. + + For untagged packets, the destination VLAN is the Primary VLAN ID of the + incoming port. So if the PVID of a port is 0, but that port is not a member of + the VLAN with ID 0, this means that untagged packets on that port are dropped. + This can be used as a roundabout way of dropping untagged packets from a port, + a mode often referred to as "Admit only tagged packets". + +1.3 Reset + + The two supported chip models do not have a sofware-initiated reset. When the + driver is initialised, as well as when the 'reset' swconfig option is invoked, + the driver will set those registers it knows about and supports to the correct + default value. But there are a lot of registers in the chip that the driver + does not support. If something changed those registers, invoking 'reset' or + performing a warm reboot might still leave the chip in a "broken" state. Only + a hardware reset will bring it back in the default state. + +2. Technical details on PHYs and the ADM6996 + + From the viewpoint of the Linux kernel, it is common that an Ethernet adapter + can be seen as a separate MAC entity and a separate PHY entity. The PHY entity + can be queried and set through registers accessible via an MDIO bus. A PHY + normally has a single address on that bus, in the range 0 through 31. + + The ADM6996 has special-purpose registers in the range of PHYs 0 through 10. + Even though all these registers control a single ADM6996 chip, the Linux + kernel treats this as 11 separate PHYs. The driver will bind to these + addresses to prevent a different PHY driver from binding and corrupting these + registers. + + What Linux sees as the PHY on address 0 is meant for the Ethernet MAC + connected to the CPU port of the ADM6996 switch chip (port 5). This is the + Ethernet MAC you will use to send and receive data through the switch. + + The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of + the switch chip. These can be accessed with the Generic PHY driver, as the + registers have the common layout. + + If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC + needs to bind to PHY address 20 for the port to work correctly. + + The ADM6996 switch driver will reset the ports 0 through 3 on startup and when + 'reset' is invoked. This could clash with a different PHY driver if the kernel + binds a PHY driver to address 16 through 19. + + If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996 + driver will simply always report a connected 100 Mbit/s full-duplex link for + that PHY, and provide no other functionality. This is most likely not what you + want. So if you see a message in your log + + ethX: PHY overlaps ADM6996, providing fixed PHY yy. + + This is most likely an indication that ethX will not work properly, and your + kernel needs to be configured to attach a different PHY to that Ethernet MAC. + + Controlling the mapping between MACs and PHYs is usually done in platform- or + board-specific fixup code. The ADM6996 driver has no influence over this. diff --git a/target/linux/generic/files-4.9/arch/mips/fw/myloader/Makefile b/target/linux/generic/files-4.9/arch/mips/fw/myloader/Makefile new file mode 100644 index 0000000000..34acfd01cc --- /dev/null +++ b/target/linux/generic/files-4.9/arch/mips/fw/myloader/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for the Compex's MyLoader support on MIPS architecture +# + +lib-y += myloader.o diff --git a/target/linux/generic/files-4.9/arch/mips/fw/myloader/myloader.c b/target/linux/generic/files-4.9/arch/mips/fw/myloader/myloader.c new file mode 100644 index 0000000000..a26f9ad3fd --- /dev/null +++ b/target/linux/generic/files-4.9/arch/mips/fw/myloader/myloader.c @@ -0,0 +1,63 @@ +/* + * Compex's MyLoader specific prom routines + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include + +#include +#include + +#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800) +#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00) +#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00) +#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00) + +static struct myloader_info myloader_info __initdata; +static int myloader_found __initdata; + +struct myloader_info * __init myloader_get_info(void) +{ + struct mylo_system_params *sysp; + struct mylo_board_params *boardp; + struct mylo_partition_table *parts; + + if (myloader_found) + return &myloader_info; + + sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR); + boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR); + parts = (struct mylo_partition_table *)(PART_TABLE_ADDR); + + printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n", + sysp->magic, boardp->magic, parts->magic); + + /* Check for some magic numbers */ + if (sysp->magic != MYLO_MAGIC_SYS_PARAMS || + boardp->magic != MYLO_MAGIC_BOARD_PARAMS || + le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS) + return NULL; + + printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n", + sysp->vid, sysp->did, sysp->svid, sysp->sdid); + + myloader_info.vid = sysp->vid; + myloader_info.did = sysp->did; + myloader_info.svid = sysp->svid; + myloader_info.sdid = sysp->sdid; + + memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs)); + + myloader_found = 1; + + return &myloader_info; +} diff --git a/target/linux/generic/files-4.9/drivers/leds/ledtrig-netdev.c b/target/linux/generic/files-4.9/drivers/leds/ledtrig-netdev.c new file mode 100644 index 0000000000..8d3249010d --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/leds/ledtrig-netdev.c @@ -0,0 +1,444 @@ +/* + * LED Kernel Netdev Trigger + * + * Toggles the LED to reflect the link and traffic state of a named net device + * + * Copyright 2007 Oliver Jowett + * + * Derived from ledtrig-timer.c which is: + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "leds.h" + +/* + * Configurable sysfs attributes: + * + * device_name - network device name to monitor + * + * interval - duration of LED blink, in milliseconds + * + * mode - either "none" (LED is off) or a space separated list of one or more of: + * link: LED's normal state reflects whether the link is up (has carrier) or not + * tx: LED blinks on transmitted data + * rx: LED blinks on receive data + * + * Some suggestions: + * + * Simple link status LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo link >someled/mode + * + * Ethernet-style link/activity LED: + * $ echo netdev >someled/trigger + * $ echo eth0 >someled/device_name + * $ echo "link tx rx" >someled/mode + * + * Modem-style tx/rx LEDs: + * $ echo netdev >led1/trigger + * $ echo ppp0 >led1/device_name + * $ echo tx >led1/mode + * $ echo netdev >led2/trigger + * $ echo ppp0 >led2/device_name + * $ echo rx >led2/mode + * + */ + +#define MODE_LINK 1 +#define MODE_TX 2 +#define MODE_RX 4 + +struct led_netdev_data { + spinlock_t lock; + + struct delayed_work work; + struct notifier_block notifier; + + struct led_classdev *led_cdev; + struct net_device *net_dev; + + char device_name[IFNAMSIZ]; + unsigned interval; + unsigned mode; + unsigned link_up; + unsigned last_activity; +}; + +static void set_baseline_state(struct led_netdev_data *trigger_data) +{ + if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) + led_set_brightness(trigger_data->led_cdev, LED_FULL); + else + led_set_brightness(trigger_data->led_cdev, LED_OFF); + + if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static ssize_t led_device_name_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%s\n", trigger_data->device_name); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_device_name_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (size >= IFNAMSIZ) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + strcpy(trigger_data->device_name, buf); + if (size > 0 && trigger_data->device_name[size-1] == '\n') + trigger_data->device_name[size-1] = 0; + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + if (trigger_data->device_name[0] != 0) { + /* check for existing device to update from */ + trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); + if (trigger_data->net_dev != NULL) + trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; + } + + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); + +static ssize_t led_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->mode == 0) { + strcpy(buf, "none\n"); + } else { + if (trigger_data->mode & MODE_LINK) + strcat(buf, "link "); + if (trigger_data->mode & MODE_TX) + strcat(buf, "tx "); + if (trigger_data->mode & MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf)+1; +} + +static ssize_t led_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode == -1) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= MODE_LINK; + else + return -EINVAL; + } + + if (new_mode == -1) + return -EINVAL; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->mode = new_mode; + set_baseline_state(trigger_data); + spin_unlock_bh(&trigger_data->lock); + + return size; +} + +static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); + +static ssize_t led_interval_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + spin_lock_bh(&trigger_data->lock); + sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); + spin_unlock_bh(&trigger_data->lock); + + return strlen(buf) + 1; +} + +static ssize_t led_interval_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long value = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (isspace(*after)) + count++; + + /* impose some basic bounds on the timer interval */ + if (count == size && value >= 5 && value <= 10000) { + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + trigger_data->interval = msecs_to_jiffies(value); + set_baseline_state(trigger_data); /* resets timer */ + spin_unlock_bh(&trigger_data->lock); + + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); + +static int netdev_trig_notify(struct notifier_block *nb, + unsigned long evt, + void *dv) +{ + struct net_device *dev = netdev_notifier_info_to_dev((struct netdev_notifier_info *) dv); + struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + + if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER && evt != NETDEV_CHANGENAME) + return NOTIFY_DONE; + + if (strcmp(dev->name, trigger_data->device_name)) + return NOTIFY_DONE; + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (evt == NETDEV_REGISTER || evt == NETDEV_CHANGENAME) { + if (trigger_data->net_dev != NULL) + dev_put(trigger_data->net_dev); + + dev_hold(dev); + trigger_data->net_dev = dev; + trigger_data->link_up = 0; + goto done; + } + + if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + goto done; + } + + /* UP / DOWN / CHANGE */ + + trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); + set_baseline_state(trigger_data); + +done: + spin_unlock_bh(&trigger_data->lock); + return NOTIFY_DONE; +} + +/* here's the real work! */ +static void netdev_trig_work(struct work_struct *work) +{ + struct led_netdev_data *trigger_data = container_of(work, struct led_netdev_data, work.work); + struct rtnl_link_stats64 *dev_stats; + unsigned new_activity; + struct rtnl_link_stats64 temp; + + if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { + /* we don't need to do timer work, just reflect link state. */ + led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); + return; + } + + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + new_activity = + ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + + ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); + + if (trigger_data->mode & MODE_LINK) { + /* base state is ON (link present) */ + /* if there's no link, we don't get this far and the LED is off */ + + /* OFF -> ON always */ + /* ON -> OFF on activity */ + if (trigger_data->led_cdev->brightness == LED_OFF) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } + } else { + /* base state is OFF */ + /* ON -> OFF always */ + /* OFF -> ON on activity */ + if (trigger_data->led_cdev->brightness == LED_FULL) { + led_set_brightness(trigger_data->led_cdev, LED_OFF); + } else if (trigger_data->last_activity != new_activity) { + led_set_brightness(trigger_data->led_cdev, LED_FULL); + } + } + + trigger_data->last_activity = new_activity; + schedule_delayed_work(&trigger_data->work, trigger_data->interval); +} + +static void netdev_trig_activate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data; + int rc; + + trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); + if (!trigger_data) + return; + + spin_lock_init(&trigger_data->lock); + + trigger_data->notifier.notifier_call = netdev_trig_notify; + trigger_data->notifier.priority = 10; + + INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); + + trigger_data->led_cdev = led_cdev; + trigger_data->net_dev = NULL; + trigger_data->device_name[0] = 0; + + trigger_data->mode = 0; + trigger_data->interval = msecs_to_jiffies(50); + trigger_data->link_up = 0; + trigger_data->last_activity = 0; + + led_cdev->trigger_data = trigger_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_device_name); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_mode); + if (rc) + goto err_out_device_name; + rc = device_create_file(led_cdev->dev, &dev_attr_interval); + if (rc) + goto err_out_mode; + + register_netdevice_notifier(&trigger_data->notifier); + return; + +err_out_mode: + device_remove_file(led_cdev->dev, &dev_attr_mode); +err_out_device_name: + device_remove_file(led_cdev->dev, &dev_attr_device_name); +err_out: + led_cdev->trigger_data = NULL; + kfree(trigger_data); +} + +static void netdev_trig_deactivate(struct led_classdev *led_cdev) +{ + struct led_netdev_data *trigger_data = led_cdev->trigger_data; + + if (trigger_data) { + unregister_netdevice_notifier(&trigger_data->notifier); + + device_remove_file(led_cdev->dev, &dev_attr_device_name); + device_remove_file(led_cdev->dev, &dev_attr_mode); + device_remove_file(led_cdev->dev, &dev_attr_interval); + + cancel_delayed_work_sync(&trigger_data->work); + + spin_lock_bh(&trigger_data->lock); + + if (trigger_data->net_dev) { + dev_put(trigger_data->net_dev); + trigger_data->net_dev = NULL; + } + + spin_unlock_bh(&trigger_data->lock); + + kfree(trigger_data); + } +} + +static struct led_trigger netdev_led_trigger = { + .name = "netdev", + .activate = netdev_trig_activate, + .deactivate = netdev_trig_deactivate, +}; + +static int __init netdev_trig_init(void) +{ + return led_trigger_register(&netdev_led_trigger); +} + +static void __exit netdev_trig_exit(void) +{ + led_trigger_unregister(&netdev_led_trigger); +} + +module_init(netdev_trig_init); +module_exit(netdev_trig_exit); + +MODULE_AUTHOR("Oliver Jowett "); +MODULE_DESCRIPTION("Netdev LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.9/drivers/misc/owl-loader.c b/target/linux/generic/files-4.9/drivers/misc/owl-loader.c new file mode 100644 index 0000000000..f11cb2b49c --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/misc/owl-loader.c @@ -0,0 +1,246 @@ +/* + * Initialize Owl Emulation Devices + * + * Copyright (C) 2016 Christian Lamparter + * Copyright (C) 2016 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) + * need to be able to initialize the PCIe wifi device. Normally, this is done + * during the early stages of booting linux, because the necessary init code + * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup. + * However,this isn't possible for devices which have the init code for the + * Atheros chip stored on NAND. Hence, this module can be used to initialze + * the chip when the user-space is ready to extract the init code. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct owl_ctx { + struct completion eeprom_load; +}; + +#define EEPROM_FILENAME_LEN 100 + +#define AR5416_EEPROM_MAGIC 0xa55a + +static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, + size_t cal_len) +{ + void __iomem *mem; + const void *cal_end = (void *)cal_data + cal_len; + const struct { + __be16 reg; + __be16 low_val; + __be16 high_val; + } __packed *data; + u16 cmd; + u32 bar0; + bool swap_needed = false; + + if (*cal_data != AR5416_EEPROM_MAGIC) { + if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { + dev_err(&pdev->dev, "invalid calibration data\n"); + return -EINVAL; + } + + dev_dbg(&pdev->dev, "calibration data needs swapping\n"); + swap_needed = true; + } + + dev_info(&pdev->dev, "fixup device configuration\n"); + + mem = pcim_iomap(pdev, 0, 0); + if (!mem) { + dev_err(&pdev->dev, "ioremap error\n"); + return -EINVAL; + } + + pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, + pci_resource_start(pdev, 0)); + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + /* set pointer to first reg address */ + for (data = (const void *) (cal_data + 3); + (const void *) data <= cal_end && data->reg != cpu_to_be16(~0); + data++) { + u32 val; + u16 reg; + + reg = data->reg; + val = data->low_val; + val |= data->high_val << 16; + + if (swap_needed) { + reg = swab16(reg); + val = swahb32(val); + } + +#ifdef CONFIG_LANTIQ + val = swab32(val); +#endif + + __raw_writel(val, mem + reg); + udelay(100); + } + + pci_read_config_word(pdev, PCI_COMMAND, &cmd); + cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); + pci_write_config_word(pdev, PCI_COMMAND, cmd); + + pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); + pcim_iounmap(pdev, mem); + + pci_disable_device(pdev); + + return 0; +} + +static void owl_fw_cb(const struct firmware *fw, void *context) +{ + struct pci_dev *pdev = (struct pci_dev *) context; + struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev); + struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct pci_bus *bus; + + complete(&ctx->eeprom_load); + + if (!fw) { + dev_err(&pdev->dev, "no eeprom data received.\n"); + goto release; + } + + /* also note that we are doing *u16 operations on the file */ + if (fw->size > sizeof(pdata->eeprom_data) || fw->size < 0x200 || + (fw->size & 1) == 1) { + dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); + goto release; + } + + if (pdata) { + memcpy(pdata->eeprom_data, fw->data, fw->size); + + /* + * eeprom has been successfully loaded - pass the data to ath9k + * but remove the eeprom_name, so it doesn't try to load it too. + */ + pdata->eeprom_name = NULL; + } + + if (ath9k_pci_fixup(pdev, (const u16 *) fw->data, fw->size)) + goto release; + + pci_lock_rescan_remove(); + bus = pdev->bus; + pci_stop_and_remove_bus_device(pdev); + /* + * the device should come back with the proper + * ProductId. But we have to initiate a rescan. + */ + pci_rescan_bus(bus); + pci_unlock_rescan_remove(); + +release: + release_firmware(fw); +} + +static const char *owl_get_eeprom_name(struct pci_dev *pdev) +{ + struct device *dev = &pdev->dev; + struct ath9k_platform_data *pdata; + char *eeprom_name; + + /* try the existing platform data first */ + pdata = dev_get_platdata(dev); + if (pdata && pdata->eeprom_name) + return pdata->eeprom_name; + + dev_dbg(dev, "using auto-generated eeprom filename\n"); + + eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); + if (!eeprom_name) + return NULL; + + /* this should match the pattern used in ath9k/init.c */ + scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", + dev_name(dev)); + + return eeprom_name; +} + +static int owl_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + struct owl_ctx *ctx; + const char *eeprom_name; + int err = 0; + + if (pcim_enable_device(pdev)) + return -EIO; + + pcim_pin_device(pdev); + + eeprom_name = owl_get_eeprom_name(pdev); + if (!eeprom_name) { + dev_err(&pdev->dev, "no eeprom filename found.\n"); + return -ENODEV; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + dev_err(&pdev->dev, "failed to alloc device context.\n"); + return -ENOMEM; + } + init_completion(&ctx->eeprom_load); + + pci_set_drvdata(pdev, ctx); + err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, + &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); + if (err) { + dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); + kfree(ctx); + } + return err; +} + +static void owl_remove(struct pci_dev *pdev) +{ + struct owl_ctx *ctx = pci_get_drvdata(pdev); + + if (ctx) { + wait_for_completion(&ctx->eeprom_load); + pci_set_drvdata(pdev, NULL); + kfree(ctx); + } +} + +static const struct pci_device_id owl_pci_table[] = { + { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ + { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ + { }, +}; +MODULE_DEVICE_TABLE(pci, owl_pci_table); + +static struct pci_driver owl_driver = { + .name = "owl-loader", + .id_table = owl_pci_table, + .probe = owl_probe, + .remove = owl_remove, +}; +module_pci_driver(owl_driver); +MODULE_AUTHOR("Christian Lamparter "); +MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Kconfig new file mode 100644 index 0000000000..81ece43db8 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Kconfig @@ -0,0 +1,76 @@ +config MTD_SPLIT + def_bool n + help + Generic MTD split support. + +config MTD_SPLIT_SUPPORT + def_bool MTD = y + +comment "Rootfs partition parsers" + +config MTD_SPLIT_SQUASHFS_ROOT + bool "Squashfs based root partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + default n + help + This provides a parsing function which allows to detect the + offset and size of the unused portion of a rootfs partition + containing a squashfs. + +comment "Firmware partition parsers" + +config MTD_SPLIT_SEAMA_FW + bool "Seama firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_WRGG_FW + bool "WRGG firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_UIMAGE_FW + bool "uImage based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_FIT_FW + bool "FIT based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_LZMA_FW + bool "LZMA compressed kernel based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TPLINK_FW + bool "TP-Link firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_TRX_FW + bool "TRX image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_BRNIMAGE_FW + bool "brnImage (brnboot image) firmware parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_EVA_FW + bool "EVA image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_MINOR_FW + bool "Mikrotik NOR image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT + +config MTD_SPLIT_JIMAGE_FW + bool "JBOOT Image based firmware partition parser" + depends on MTD_SPLIT_SUPPORT + select MTD_SPLIT diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Makefile new file mode 100644 index 0000000000..206e754a18 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/Makefile @@ -0,0 +1,13 @@ +obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o +obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o +obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o +obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o +obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o +obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o +obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o +obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o +obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o +obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o +obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o +obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o +obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.c new file mode 100644 index 0000000000..b2e51dcfc6 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.c @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) "mtdsplit: " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define UBI_EC_MAGIC 0x55424923 /* UBI# */ + +struct squashfs_super_block { + __le32 s_magic; + __le32 pad0[9]; + __le64 bytes_used; +}; + +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + struct squashfs_super_block sb; + size_t retlen; + int err; + + err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb); + if (err || (retlen != sizeof(sb))) { + pr_alert("error occured while reading from \"%s\"\n", + master->name); + return -EIO; + } + + if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) { + pr_alert("no squashfs found in \"%s\"\n", master->name); + return -EINVAL; + } + + retlen = le64_to_cpu(sb.bytes_used); + if (retlen <= 0) { + pr_alert("squashfs is empty in \"%s\"\n", master->name); + return -ENODEV; + } + + if (offset + retlen > master->size) { + pr_alert("squashfs has invalid size in \"%s\"\n", + master->name); + return -EINVAL; + } + + *squashfs_len = retlen; + return 0; +} +EXPORT_SYMBOL_GPL(mtd_get_squashfs_len); + +static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset) +{ + return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize; +} + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + u32 magic; + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, sizeof(magic), &retlen, + (unsigned char *) &magic); + if (ret) + return ret; + + if (retlen != sizeof(magic)) + return -EIO; + + if (le32_to_cpu(magic) == SQUASHFS_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_SQUASHFS; + return 0; + } else if (magic == 0x19852003) { + if (type) + *type = MTDSPLIT_PART_TYPE_JFFS2; + return 0; + } else if (be32_to_cpu(magic) == UBI_EC_MAGIC) { + if (type) + *type = MTDSPLIT_PART_TYPE_UBI; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + size_t offset; + int err; + + for (offset = from; offset < limit; + offset = mtd_next_eb(mtd, offset)) { + err = mtd_check_rootfs_magic(mtd, offset, type); + if (err) + continue; + + *ret_offset = offset; + return 0; + } + + return -ENODEV; +} +EXPORT_SYMBOL_GPL(mtd_find_rootfs_from); + diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.h b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.h new file mode 100644 index 0000000000..71d62a8956 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009-2013 Felix Fietkau + * Copyright (C) 2009-2013 Gabor Juhos + * Copyright (C) 2012 Jonas Gorski + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MTDSPLIT_H +#define _MTDSPLIT_H + +#define KERNEL_PART_NAME "kernel" +#define ROOTFS_PART_NAME "rootfs" +#define UBI_PART_NAME "ubi" + +#define ROOTFS_SPLIT_NAME "rootfs_data" + +enum mtdsplit_part_type { + MTDSPLIT_PART_TYPE_UNK = 0, + MTDSPLIT_PART_TYPE_SQUASHFS, + MTDSPLIT_PART_TYPE_JFFS2, + MTDSPLIT_PART_TYPE_UBI, +}; + +#ifdef CONFIG_MTD_SPLIT +int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len); + +int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type); + +int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type); + +#else +static inline int mtd_get_squashfs_len(struct mtd_info *master, + size_t offset, + size_t *squashfs_len) +{ + return -ENODEV; +} + +static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, + enum mtdsplit_part_type *type) +{ + return -EINVAL; +} + +static inline int mtd_find_rootfs_from(struct mtd_info *mtd, + size_t from, + size_t limit, + size_t *ret_offset, + enum mtdsplit_part_type *type) +{ + return -ENODEV; +} +#endif /* CONFIG_MTD_SPLIT */ + +#endif /* _MTDSPLIT_H */ diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_brnimage.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_brnimage.c new file mode 100644 index 0000000000..3f2d79601a --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_brnimage.c @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define BRNIMAGE_NR_PARTS 2 + +#define BRNIMAGE_ALIGN_BYTES 0x400 +#define BRNIMAGE_FOOTER_SIZE 12 + +#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE) +#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE) + +static int mtdsplit_parse_brnimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + uint32_t buf; + unsigned long rootfs_offset, rootfs_size, kernel_size; + size_t len; + int ret = 0; + + for (rootfs_offset = 0; rootfs_offset < master->size; + rootfs_offset += BRNIMAGE_ALIGN_BYTES) { + ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (!ret) + break; + } + + if (ret) + return ret; + + if (rootfs_offset >= master->size) + return -EINVAL; + + ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len, + (void *)&buf); + if (ret) + return ret; + + if (len != 4) + return -EIO; + + kernel_size = le32_to_cpu(buf); + + if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD)) + return -EINVAL; + + if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD)) + return -EINVAL; + + /* + * The footer must be untouched as it contains the checksum of the + * original brnImage (kernel + squashfs)! + */ + rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE; + + parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return BRNIMAGE_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_brnimage_parser = { + .owner = THIS_MODULE, + .name = "brnimage-fw", + .parse_fn = mtdsplit_parse_brnimage, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_brnimage_init(void) +{ + register_mtd_parser(&mtdsplit_brnimage_parser); + + return 0; +} + +subsys_initcall(mtdsplit_brnimage_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_eva.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_eva.c new file mode 100644 index 0000000000..746944ee2e --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_eva.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2012 John Crispin + * Copyright (C) 2015 Martin Blumenstingl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define EVA_NR_PARTS 2 +#define EVA_MAGIC 0xfeed1281 +#define EVA_FOOTER_SIZE 0x18 +#define EVA_DUMMY_SQUASHFS_SIZE 0x100 + +struct eva_image_header { + uint32_t magic; + uint32_t size; +}; + +static int mtdsplit_parse_eva(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct eva_image_header hdr; + size_t retlen; + unsigned long kernel_size, rootfs_offset; + int err; + + err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != sizeof(hdr)) + return -EIO; + + if (le32_to_cpu(hdr.magic) != EVA_MAGIC) + return -EINVAL; + + kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE; + + /* rootfs starts at the next 0x10000 boundary: */ + rootfs_offset = round_up(kernel_size, 0x10000); + + /* skip the dummy EVA squashfs partition (with wrong endianness): */ + rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE; + + if (rootfs_offset >= master->size) + return -EINVAL; + + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return EVA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_eva_parser = { + .owner = THIS_MODULE, + .name = "eva-fw", + .parse_fn = mtdsplit_parse_eva, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_eva_init(void) +{ + register_mtd_parser(&mtdsplit_eva_parser); + + return 0; +} + +subsys_initcall(mtdsplit_eva_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_fit.c new file mode 100644 index 0000000000..f356adcd4e --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_fit.c @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2015 The Linux Foundation + * Copyright (C) 2014 Gabor Juhos + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +struct fdt_header { + uint32_t magic; /* magic word FDT_MAGIC */ + uint32_t totalsize; /* total size of DT block */ + uint32_t off_dt_struct; /* offset to structure */ + uint32_t off_dt_strings; /* offset to strings */ + uint32_t off_mem_rsvmap; /* offset to memory reserve map */ + uint32_t version; /* format version */ + uint32_t last_comp_version; /* last compatible version */ + + /* version 2 fields below */ + uint32_t boot_cpuid_phys; /* Which physical CPU id we're + booting on */ + /* version 3 fields below */ + uint32_t size_dt_strings; /* size of the strings block */ + + /* version 17 fields below */ + uint32_t size_dt_struct; /* size of the structure block */ +}; + +static int +mtdsplit_fit_parse(struct mtd_info *mtd, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct fdt_header hdr; + size_t hdr_len, retlen; + size_t offset; + size_t fit_offset, fit_size; + size_t rootfs_offset, rootfs_size; + struct mtd_partition *parts; + int ret; + + hdr_len = sizeof(struct fdt_header); + + /* Parse the MTD device & search for the FIT image location */ + for(offset = 0; offset < mtd->size; offset += mtd->erasesize) { + ret = mtd_read(mtd, 0, hdr_len, &retlen, (void*) &hdr); + if (ret) { + pr_err("read error in \"%s\" at offset 0x%llx\n", + mtd->name, (unsigned long long) offset); + return ret; + } + + if (retlen != hdr_len) { + pr_err("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + /* Check the magic - see if this is a FIT image */ + if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) { + pr_debug("no valid FIT image found in \"%s\" at offset %llx\n", + mtd->name, (unsigned long long) offset); + continue; + } + + /* We found a FIT image. Let's keep going */ + break; + } + + fit_offset = offset; + fit_size = be32_to_cpu(hdr.totalsize); + + if (fit_size == 0) { + pr_err("FIT image in \"%s\" at offset %llx has null size\n", + mtd->name, (unsigned long long) fit_offset); + return -ENODEV; + } + + /* Search for the rootfs partition after the FIT image */ + ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, + &rootfs_offset, NULL); + if (ret) { + pr_info("no rootfs found after FIT image in \"%s\"\n", + mtd->name); + return ret; + } + + rootfs_size = mtd->size - rootfs_offset; + + parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = fit_offset; + parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return 2; +} + +static struct mtd_part_parser uimage_parser = { + .owner = THIS_MODULE, + .name = "fit-fw", + .parse_fn = mtdsplit_fit_parse, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_fit_init(void) +{ + register_mtd_parser(&uimage_parser); + + return 0; +} + +module_init(mtdsplit_fit_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_jimage.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_jimage.c new file mode 100644 index 0000000000..51544a794b --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_jimage.c @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2018 Paweł Dembicki + * + * Based on: mtdsplit_uimage.c + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE ) + +#define STAG_SIZE 16 +#define STAG_ID 0x04 +#define STAG_MAGIC 0x2B24 + +#define SCH2_SIZE 40 +#define SCH2_MAGIC 0x2124 +#define SCH2_VER 0x02 + +/* + * Jboot image header, + * all data in little endian. + */ + +struct jimage_header //stag + sch2 jboot joined headers +{ + uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id + uint8_t stag_id; // 0x04 + uint16_t stag_magic; //magic 0x2B24 + uint32_t stag_time_stamp; // timestamp calculated in jboot way + uint32_t stag_image_length; // lentgh of kernel + sch2 header + uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel + uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data + uint16_t sch2_magic; // magic 0x2124 + uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma + uint8_t sch2_version; // 0x02 for sch2 + uint32_t sch2_ram_addr; // ram entry address + uint32_t sch2_image_len; // kernel image length + uint32_t sch2_image_crc32; // kernel image crc + uint32_t sch2_start_addr; // ram start address + uint32_t sch2_rootfs_addr; // rootfs flash address + uint32_t sch2_rootfs_len; // rootfls length + uint32_t sch2_rootfs_crc32; // rootfs crc32 + uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero + uint16_t sch2_header_length; // sch2 header length: 0x28 + uint16_t sch2_cmd_line_length; // cmd line length, known zeros +}; + +static int +read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid jImage header if found + */ +static int __mtdsplit_parse_jimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t jimage_offset; + size_t jimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int jimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find jImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct jimage_header *header; + + jimage_size = 0; + + ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid jImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct jimage_header *)(buf + ret); + + jimage_size = sizeof(*header) + header->sch2_image_len + ret; + if ((offset + jimage_size) > master->size) { + pr_debug("jImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (jimage_size == 0) { + pr_debug("no jImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + jimage_offset = offset; + + if (jimage_offset == 0) { + jimage_part = 0; + rf_part = 1; + + /* find the roots after the jImage */ + ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + jimage_size = rootfs_offset - jimage_offset; + } else { + rf_part = 0; + jimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before jImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = jimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[jimage_part].name = KERNEL_PART_NAME; + parts[jimage_part].offset = jimage_offset; + parts[jimage_part].size = jimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t jimage_verify_default(u_char *buf, size_t len) +{ + struct jimage_header *header = (struct jimage_header *)buf; + + /* default sanity checks */ + if (header->stag_magic != STAG_MAGIC) { + pr_debug("invalid jImage stag header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_magic != SCH2_MAGIC) { + pr_debug("invalid jImage sch2 header magic: %04x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_cmark != header->stag_id) { + pr_debug("invalid jImage stag header cmark: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->stag_id != STAG_ID) { + pr_debug("invalid jImage stag header id: %02x\n", + header->stag_magic); + return -EINVAL; + } + if (header->sch2_version != SCH2_VER) { + pr_debug("invalid jImage sch2 header version: %02x\n", + header->stag_magic); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_jimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_jimage(master, pparts, data, + jimage_verify_default); +} + +static struct mtd_part_parser jimage_generic_parser = { + .owner = THIS_MODULE, + .name = "jimage-fw", + .parse_fn = mtdsplit_jimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_jimage_init(void) +{ + register_mtd_parser(&jimage_generic_parser); + + return 0; +} + +module_init(mtdsplit_jimage_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_lzma.c new file mode 100644 index 0000000000..b7f044a6dc --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_lzma.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "mtdsplit.h" + +#define LZMA_NR_PARTS 2 +#define LZMA_PROPERTIES_SIZE 5 + +struct lzma_header { + u8 props[LZMA_PROPERTIES_SIZE]; + u8 size_low[4]; + u8 size_high[4]; +}; + +static int mtdsplit_parse_lzma(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct lzma_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + u32 t; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* verify LZMA properties */ + if (hdr.props[0] >= (9 * 5 * 5)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.props[1]); + if (!is_power_of_2(t)) + return -EINVAL; + + t = get_unaligned_le32(&hdr.size_high); + if (t) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return LZMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_lzma_parser = { + .owner = THIS_MODULE, + .name = "lzma-fw", + .parse_fn = mtdsplit_parse_lzma, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_lzma_init(void) +{ + register_mtd_parser(&mtdsplit_lzma_parser); + + return 0; +} + +subsys_initcall(mtdsplit_lzma_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_minor.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_minor.c new file mode 100644 index 0000000000..f971f0a650 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_minor.c @@ -0,0 +1,117 @@ +/* + * MTD splitter for MikroTik NOR devices + * + * Copyright (C) 2017 Thibaut VARENE + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * The rootfs is expected at erase-block boundary due to the use of + * mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header + * for two main reasons: + * - the original header uses weakly defined types (int, enum...) which can + * vary in length depending on build host (and the struct is not packed), + * and the name field can have a different total length depending on + * whether or not the yaffs code was _built_ with unicode support. + * - the only field that could be of real use here (file_size_low) contains + * invalid data in the header generated by kernel2minor, so we cannot use + * it to infer the exact position of the rootfs and do away with + * mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs). + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define YAFFS_OBJECT_TYPE_FILE 0x1 +#define YAFFS_OBJECTID_ROOT 0x1 +#define YAFFS_SUM_UNUSED 0xFFFF +#define YAFFS_NAME "kernel" + +#define MINOR_NR_PARTS 2 + +/* + * This structure is based on yaffs_obj_hdr from yaffs_guts.h + * The weak types match upstream. The fields have cpu-endianness + */ +struct minor_header { + int yaffs_type; + int yaffs_obj_id; + u16 yaffs_sum_unused; + char yaffs_name[sizeof(YAFFS_NAME)]; +}; + +static int mtdsplit_parse_minor(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct minor_header hdr; + size_t hdr_len, retlen; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* match header */ + if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) + return -EINVAL; + + if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) + return -EINVAL; + + if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) + return -EINVAL; + + if (memcmp(hdr.yaffs_name, YAFFS_NAME, sizeof(YAFFS_NAME))) + return -EINVAL; + + err = mtd_find_rootfs_from(master, master->erasesize, master->size, + &rootfs_offset, NULL); + if (err) + return err; + + parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return MINOR_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_minor_parser = { + .owner = THIS_MODULE, + .name = "minor-fw", + .parse_fn = mtdsplit_parse_minor, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_minor_init(void) +{ + register_mtd_parser(&mtdsplit_minor_parser); + + return 0; +} + +subsys_initcall(mtdsplit_minor_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_seama.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_seama.c new file mode 100644 index 0000000000..f8556e05a4 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_seama.c @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define SEAMA_MAGIC 0x5EA3A417 +#define SEAMA_NR_PARTS 2 +#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +struct seama_header { + __be32 magic; /* should always be SEAMA_MAGIC. */ + __be16 reserved; /* reserved for */ + __be16 metasize; /* size of the META data */ + __be32 size; /* size of the image */ + u8 md5[16]; /* digest */ +}; + +static int mtdsplit_parse_seama(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct seama_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) + return -EINVAL; + + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) + + be16_to_cpu(hdr.metasize); + if (kernel_ent_size > master->size) + return -EINVAL; + + /* Check for the rootfs right after Seama entity with a kernel. */ + err = mtd_check_rootfs_magic(master, kernel_ent_size, &type); + if (!err) { + rootfs_offset = kernel_ent_size; + } else { + /* + * On some devices firmware entity might contain both: kernel + * and rootfs. We can't determine kernel size so we just have to + * look for rootfs magic. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + } + + parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize); + parts[0].size = rootfs_offset - parts[0].offset; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[1].name = UBI_PART_NAME; + else + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return SEAMA_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_seama_parser = { + .owner = THIS_MODULE, + .name = "seama-fw", + .parse_fn = mtdsplit_parse_seama, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_seama_init(void) +{ + register_mtd_parser(&mtdsplit_seama_parser); + + return 0; +} + +subsys_initcall(mtdsplit_seama_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_squashfs.c new file mode 100644 index 0000000000..79e1f73bca --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_squashfs.c @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Felix Fietkau + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +static int +mtdsplit_parse_squashfs(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *part; + struct mtd_info *parent_mtd; + size_t part_offset; + size_t squashfs_len; + int err; + + err = mtd_get_squashfs_len(master, 0, &squashfs_len); + if (err) + return err; + + parent_mtd = mtdpart_get_master(master); + part_offset = mtdpart_get_offset(master); + + part = kzalloc(sizeof(*part), GFP_KERNEL); + if (!part) { + pr_alert("unable to allocate memory for \"%s\" partition\n", + ROOTFS_SPLIT_NAME); + return -ENOMEM; + } + + part->name = ROOTFS_SPLIT_NAME; + part->offset = mtd_roundup_to_eb(part_offset + squashfs_len, + parent_mtd) - part_offset; + part->size = mtd_rounddown_to_eb(master->size - part->offset, master); + + *pparts = part; + return 1; +} + +static struct mtd_part_parser mtdsplit_squashfs_parser = { + .owner = THIS_MODULE, + .name = "squashfs-split", + .parse_fn = mtdsplit_parse_squashfs, + .type = MTD_PARSER_TYPE_ROOTFS, +}; + +static int __init mtdsplit_squashfs_init(void) +{ + register_mtd_parser(&mtdsplit_squashfs_parser); + + return 0; +} + +subsys_initcall(mtdsplit_squashfs_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_tplink.c new file mode 100644 index 0000000000..c346aa8130 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_tplink.c @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TPLINK_NR_PARTS 2 +#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ + +#define MD5SUM_LEN 16 + +struct fw_v1 { + char vendor_name[24]; + char fw_version[36]; + uint32_t hw_id; /* hardware id */ + uint32_t hw_rev; /* hardware revision */ + uint32_t unk1; + uint8_t md5sum1[MD5SUM_LEN]; + uint32_t unk2; + uint8_t md5sum2[MD5SUM_LEN]; + uint32_t unk3; + uint32_t kernel_la; /* kernel load address */ + uint32_t kernel_ep; /* kernel entry point */ + uint32_t fw_length; /* total length of the firmware */ + uint32_t kernel_ofs; /* kernel data offset */ + uint32_t kernel_len; /* kernel data length */ + uint32_t rootfs_ofs; /* rootfs data offset */ + uint32_t rootfs_len; /* rootfs data length */ + uint32_t boot_ofs; /* bootloader data offset */ + uint32_t boot_len; /* bootloader data length */ + uint8_t pad[360]; +} __attribute__ ((packed)); + +struct fw_v2 { + char fw_version[48]; /* 0x04: fw version string */ + uint32_t hw_id; /* 0x34: hardware id */ + uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ + uint32_t unk1; /* 0x3c: 0x00000000 */ + uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ + uint32_t unk2; /* 0x50: 0x00000000 */ + uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ + uint32_t unk3; /* 0x64: 0xffffffff */ + + uint32_t kernel_la; /* 0x68: kernel load address */ + uint32_t kernel_ep; /* 0x6c: kernel entry point */ + uint32_t fw_length; /* 0x70: total length of the image */ + uint32_t kernel_ofs; /* 0x74: kernel data offset */ + uint32_t kernel_len; /* 0x78: kernel data length */ + uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ + uint32_t rootfs_len; /* 0x80: rootfs data length */ + uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ + uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ + uint16_t unk4; /* 0x8c: 0x55aa */ + uint8_t sver_hi; /* 0x8e */ + uint8_t sver_lo; /* 0x8f */ + uint8_t unk5; /* 0x90: magic: 0xa5 */ + uint8_t ver_hi; /* 0x91 */ + uint8_t ver_mid; /* 0x92 */ + uint8_t ver_lo; /* 0x93 */ + uint8_t pad[364]; +} __attribute__ ((packed)); + +struct tplink_fw_header { + uint32_t version; + union { + struct fw_v1 v1; + struct fw_v2 v2; + }; +}; + +static int mtdsplit_parse_tplink(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct tplink_fw_header hdr; + size_t hdr_len, retlen, kernel_size; + size_t rootfs_offset; + struct mtd_partition *parts; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + switch (le32_to_cpu(hdr.version)) { + case 1: + if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs); + break; + case 2: + case 3: + if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) + return -EINVAL; + + kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); + rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs); + break; + default: + return -EINVAL; + } + + if (kernel_size > master->size) + return -EINVAL; + + /* Find the rootfs */ + err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); + if (err) { + /* + * The size in the header might cover the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, NULL); + if (err) + return err; + } + + parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = kernel_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return TPLINK_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_tplink_parser = { + .owner = THIS_MODULE, + .name = "tplink-fw", + .parse_fn = mtdsplit_parse_tplink, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_tplink_init(void) +{ + register_mtd_parser(&mtdsplit_tplink_parser); + + return 0; +} + +subsys_initcall(mtdsplit_tplink_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_trx.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_trx.c new file mode 100644 index 0000000000..53aebc5987 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_trx.c @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ + +struct trx_header { + __le32 magic; + __le32 len; + __le32 crc32; + __le32 flag_version; + __le32 offset[4]; +}; + +static int +read_trx_header(struct mtd_info *mtd, size_t offset, + struct trx_header *header) +{ + size_t header_len; + size_t retlen; + int ret; + + header_len = sizeof(*header); + ret = mtd_read(mtd, offset, header_len, &retlen, + (unsigned char *) header); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +static int +mtdsplit_parse_trx(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct mtd_partition *parts; + struct trx_header hdr; + int nr_parts; + size_t offset; + size_t trx_offset; + size_t trx_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int ret; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + /* find trx image on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + trx_size = 0; + + ret = read_trx_header(master, offset, &hdr); + if (ret) + continue; + + if (hdr.magic != cpu_to_le32(TRX_MAGIC)) { + pr_debug("no valid trx header found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + + trx_size = le32_to_cpu(hdr.len); + if ((offset + trx_size) > master->size) { + pr_debug("trx image exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (trx_size == 0) { + pr_debug("no trx header found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + trx_offset = offset + hdr.offset[0]; + rootfs_offset = offset + hdr.offset[1]; + rootfs_size = master->size - rootfs_offset; + trx_size = rootfs_offset - trx_offset; + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err; + } + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = trx_offset; + parts[0].size = trx_size; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = rootfs_size; + + *pparts = parts; + return nr_parts; + +err: + kfree(parts); + return ret; +} + +static struct mtd_part_parser trx_parser = { + .owner = THIS_MODULE, + .name = "trx-fw", + .parse_fn = mtdsplit_parse_trx, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_trx_init(void) +{ + register_mtd_parser(&trx_parser); + + return 0; +} + +module_init(mtdsplit_trx_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_uimage.c new file mode 100644 index 0000000000..bd1c723e74 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_uimage.c @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +/* + * uimage_header itself is only 64B, but it may be prepended with another data. + * Currently the biggest size is for Edimax devices: 20B + 64B + */ +#define MAX_HEADER_LEN 84 + +#define IH_MAGIC 0x27051956 /* Image Magic Number */ +#define IH_NMLEN 32 /* Image Name Length */ + +#define IH_OS_LINUX 5 /* Linux */ + +#define IH_TYPE_KERNEL 2 /* OS Kernel Image */ +#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image */ + +/* + * Legacy format image header, + * all data in network byte order (aka natural aka bigendian). + */ +struct uimage_header { + uint32_t ih_magic; /* Image Header Magic Number */ + uint32_t ih_hcrc; /* Image Header CRC Checksum */ + uint32_t ih_time; /* Image Creation Timestamp */ + uint32_t ih_size; /* Image Data Size */ + uint32_t ih_load; /* Data Load Address */ + uint32_t ih_ep; /* Entry Point Address */ + uint32_t ih_dcrc; /* Image Data CRC Checksum */ + uint8_t ih_os; /* Operating System */ + uint8_t ih_arch; /* CPU architecture */ + uint8_t ih_type; /* Image Type */ + uint8_t ih_comp; /* Compression Type */ + uint8_t ih_name[IH_NMLEN]; /* Image Name */ +}; + +static int +read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, + size_t header_len) +{ + size_t retlen; + int ret; + + ret = mtd_read(mtd, offset, header_len, &retlen, buf); + if (ret) { + pr_debug("read error in \"%s\"\n", mtd->name); + return ret; + } + + if (retlen != header_len) { + pr_debug("short read in \"%s\"\n", mtd->name); + return -EIO; + } + + return 0; +} + +/** + * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts + * + * @find_header: function to call for a block of data that will return offset + * of a valid uImage header if found + */ +static int __mtdsplit_parse_uimage(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data, + ssize_t (*find_header)(u_char *buf, size_t len)) +{ + struct mtd_partition *parts; + u_char *buf; + int nr_parts; + size_t offset; + size_t uimage_offset; + size_t uimage_size = 0; + size_t rootfs_offset; + size_t rootfs_size = 0; + int uimage_part, rf_part; + int ret; + enum mtdsplit_part_type type; + + nr_parts = 2; + parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + buf = vmalloc(MAX_HEADER_LEN); + if (!buf) { + ret = -ENOMEM; + goto err_free_parts; + } + + /* find uImage on erase block boundaries */ + for (offset = 0; offset < master->size; offset += master->erasesize) { + struct uimage_header *header; + + uimage_size = 0; + + ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN); + if (ret) + continue; + + ret = find_header(buf, MAX_HEADER_LEN); + if (ret < 0) { + pr_debug("no valid uImage found in \"%s\" at offset %llx\n", + master->name, (unsigned long long) offset); + continue; + } + header = (struct uimage_header *)(buf + ret); + + uimage_size = sizeof(*header) + be32_to_cpu(header->ih_size) + ret; + if ((offset + uimage_size) > master->size) { + pr_debug("uImage exceeds MTD device \"%s\"\n", + master->name); + continue; + } + break; + } + + if (uimage_size == 0) { + pr_debug("no uImage found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + uimage_offset = offset; + + if (uimage_offset == 0) { + uimage_part = 0; + rf_part = 1; + + /* find the roots after the uImage */ + ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size, + master->size, &rootfs_offset, &type); + if (ret) { + pr_debug("no rootfs after uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_size = master->size - rootfs_offset; + uimage_size = rootfs_offset - uimage_offset; + } else { + rf_part = 0; + uimage_part = 1; + + /* check rootfs presence at offset 0 */ + ret = mtd_check_rootfs_magic(master, 0, &type); + if (ret) { + pr_debug("no rootfs before uImage in \"%s\"\n", + master->name); + goto err_free_buf; + } + + rootfs_offset = 0; + rootfs_size = uimage_offset; + } + + if (rootfs_size == 0) { + pr_debug("no rootfs found in \"%s\"\n", master->name); + ret = -ENODEV; + goto err_free_buf; + } + + parts[uimage_part].name = KERNEL_PART_NAME; + parts[uimage_part].offset = uimage_offset; + parts[uimage_part].size = uimage_size; + + if (type == MTDSPLIT_PART_TYPE_UBI) + parts[rf_part].name = UBI_PART_NAME; + else + parts[rf_part].name = ROOTFS_PART_NAME; + parts[rf_part].offset = rootfs_offset; + parts[rf_part].size = rootfs_size; + + vfree(buf); + + *pparts = parts; + return nr_parts; + +err_free_buf: + vfree(buf); + +err_free_parts: + kfree(parts); + return ret; +} + +static ssize_t uimage_verify_default(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + + /* default sanity checks */ + if (be32_to_cpu(header->ih_magic) != IH_MAGIC) { + pr_debug("invalid uImage magic: %08x\n", + be32_to_cpu(header->ih_magic)); + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX) { + pr_debug("invalid uImage OS: %08x\n", + be32_to_cpu(header->ih_os)); + return -EINVAL; + } + + if (header->ih_type != IH_TYPE_KERNEL) { + pr_debug("invalid uImage type: %08x\n", + be32_to_cpu(header->ih_type)); + return -EINVAL; + } + + return 0; +} + +static int +mtdsplit_uimage_parse_generic(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_default); +} + +static struct mtd_part_parser uimage_generic_parser = { + .owner = THIS_MODULE, + .name = "uimage-fw", + .parse_fn = mtdsplit_uimage_parse_generic, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +#define FW_MAGIC_WNR2000V1 0x32303031 +#define FW_MAGIC_WNR2000V3 0x32303033 +#define FW_MAGIC_WNR2000V4 0x32303034 +#define FW_MAGIC_WNR2200 0x32323030 +#define FW_MAGIC_WNR612V2 0x32303631 +#define FW_MAGIC_WNR1000V2 0x31303031 +#define FW_MAGIC_WNR1000V2_VC 0x31303030 +#define FW_MAGIC_WNDR3700 0x33373030 +#define FW_MAGIC_WNDR3700V2 0x33373031 +#define FW_MAGIC_WPN824N 0x31313030 + +static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len) +{ + struct uimage_header *header = (struct uimage_header *)buf; + uint8_t expected_type = IH_TYPE_FILESYSTEM; + + switch (be32_to_cpu(header->ih_magic)) { + case FW_MAGIC_WNR612V2: + case FW_MAGIC_WNR1000V2: + case FW_MAGIC_WNR1000V2_VC: + case FW_MAGIC_WNR2000V1: + case FW_MAGIC_WNR2000V3: + case FW_MAGIC_WNR2200: + case FW_MAGIC_WNDR3700: + case FW_MAGIC_WNDR3700V2: + case FW_MAGIC_WPN824N: + break; + case FW_MAGIC_WNR2000V4: + expected_type = IH_TYPE_KERNEL; + break; + default: + return -EINVAL; + } + + if (header->ih_os != IH_OS_LINUX || + header->ih_type != expected_type) + return -EINVAL; + + return 0; +} + +static int +mtdsplit_uimage_parse_netgear(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_verify_wndr3700); +} + +static struct mtd_part_parser uimage_netgear_parser = { + .owner = THIS_MODULE, + .name = "netgear-fw", + .parse_fn = mtdsplit_uimage_parse_netgear, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Edimax + **************************************************/ + +#define FW_EDIMAX_OFFSET 20 +#define FW_MAGIC_EDIMAX 0x43535953 + +static ssize_t uimage_find_edimax(u_char *buf, size_t len) +{ + u32 *magic; + + if (len < FW_EDIMAX_OFFSET + sizeof(struct uimage_header)) { + pr_err("Buffer too small for checking Edimax header\n"); + return -ENOSPC; + } + + magic = (u32 *)buf; + if (be32_to_cpu(*magic) != FW_MAGIC_EDIMAX) + return -EINVAL; + + if (!uimage_verify_default(buf + FW_EDIMAX_OFFSET, len)) + return FW_EDIMAX_OFFSET; + + return -EINVAL; +} + +static int +mtdsplit_uimage_parse_edimax(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + return __mtdsplit_parse_uimage(master, pparts, data, + uimage_find_edimax); +} + +static struct mtd_part_parser uimage_edimax_parser = { + .owner = THIS_MODULE, + .name = "edimax-fw", + .parse_fn = mtdsplit_uimage_parse_edimax, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +/************************************************** + * Init + **************************************************/ + +static int __init mtdsplit_uimage_init(void) +{ + register_mtd_parser(&uimage_generic_parser); + register_mtd_parser(&uimage_netgear_parser); + register_mtd_parser(&uimage_edimax_parser); + + return 0; +} + +module_init(mtdsplit_uimage_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_wrgg.c b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_wrgg.c new file mode 100644 index 0000000000..16ebd51345 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/mtdsplit/mtdsplit_wrgg.c @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2013 Gabor Juhos + * Copyright (C) 2014 Felix Fietkau + * Copyright (C) 2016 Stijn Tintel + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtdsplit.h" + +#define WRGG_NR_PARTS 2 +#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ +#define WRGG03_MAGIC 0x20080321 +#define WRG_MAGIC 0x20040220 + +struct wrgg03_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + char version[16]; + char model[16]; + uint32_t flag[2]; + uint32_t reserve[2]; + char buildno[16]; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + +struct wrg_header { + char signature[32]; + uint32_t magic1; + uint32_t magic2; + uint32_t size; + uint32_t offset; + char devname[32]; + char digest[16]; +} __attribute__ ((packed)); + + +static int mtdsplit_parse_wrgg(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct wrgg03_header hdr; + size_t hdr_len, retlen, kernel_ent_size; + size_t rootfs_offset; + struct mtd_partition *parts; + enum mtdsplit_part_type type; + int err; + + hdr_len = sizeof(hdr); + err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); + if (err) + return err; + + if (retlen != hdr_len) + return -EIO; + + /* sanity checks */ + if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) { + kernel_ent_size = hdr_len + be32_to_cpu(hdr.size); + } else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) { + kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu( + ((struct wrg_header*)&hdr)->size); + } else { + return -EINVAL; + } + + if (kernel_ent_size > master->size) + return -EINVAL; + + /* + * The size in the header covers the rootfs as well. + * Start the search from an arbitrary offset. + */ + err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS, + master->size, &rootfs_offset, &type); + if (err) + return err; + + parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL); + if (!parts) + return -ENOMEM; + + parts[0].name = KERNEL_PART_NAME; + parts[0].offset = 0; + parts[0].size = rootfs_offset; + + parts[1].name = ROOTFS_PART_NAME; + parts[1].offset = rootfs_offset; + parts[1].size = master->size - rootfs_offset; + + *pparts = parts; + return WRGG_NR_PARTS; +} + +static struct mtd_part_parser mtdsplit_wrgg_parser = { + .owner = THIS_MODULE, + .name = "wrgg-fw", + .parse_fn = mtdsplit_parse_wrgg, + .type = MTD_PARSER_TYPE_FIRMWARE, +}; + +static int __init mtdsplit_wrgg_init(void) +{ + register_mtd_parser(&mtdsplit_wrgg_parser); + + return 0; +} + +subsys_initcall(mtdsplit_wrgg_init); diff --git a/target/linux/generic/files-4.9/drivers/mtd/myloader.c b/target/linux/generic/files-4.9/drivers/mtd/myloader.c new file mode 100644 index 0000000000..7532d454ed --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/mtd/myloader.c @@ -0,0 +1,182 @@ +/* + * Parse MyLoader-style flash partition tables and produce a Linux partition + * array to match. + * + * Copyright (C) 2007-2009 Gabor Juhos + * + * This file was based on drivers/mtd/redboot.c + * Author: Red Hat, Inc. - David Woodhouse + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BLOCK_LEN_MIN 0x10000 +#define PART_NAME_LEN 32 + +struct part_data { + struct mylo_partition_table tab; + char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; +}; + +static int myloader_parse_partitions(struct mtd_info *master, + const struct mtd_partition **pparts, + struct mtd_part_parser_data *data) +{ + struct part_data *buf; + struct mylo_partition_table *tab; + struct mylo_partition *part; + struct mtd_partition *mtd_parts; + struct mtd_partition *mtd_part; + int num_parts; + int ret, i; + size_t retlen; + char *names; + unsigned long offset; + unsigned long blocklen; + + buf = vmalloc(sizeof(*buf)); + if (!buf) { + return -ENOMEM; + goto out; + } + tab = &buf->tab; + + blocklen = master->erasesize; + if (blocklen < BLOCK_LEN_MIN) + blocklen = BLOCK_LEN_MIN; + + offset = blocklen; + + /* Find the partition table */ + for (i = 0; i < 4; i++, offset += blocklen) { + printk(KERN_DEBUG "%s: searching for MyLoader partition table" + " at offset 0x%lx\n", master->name, offset); + + ret = mtd_read(master, offset, sizeof(*buf), &retlen, + (void *)buf); + if (ret) + goto out_free_buf; + + if (retlen != sizeof(*buf)) { + ret = -EIO; + goto out_free_buf; + } + + /* Check for Partition Table magic number */ + if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) + break; + + } + + if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { + printk(KERN_DEBUG "%s: no MyLoader partition table found\n", + master->name); + ret = 0; + goto out_free_buf; + } + + /* The MyLoader and the Partition Table is always present */ + num_parts = 2; + + /* Detect number of used partitions */ + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + num_parts++; + } + + mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + + num_parts * PART_NAME_LEN), GFP_KERNEL); + + if (!mtd_parts) { + ret = -ENOMEM; + goto out_free_buf; + } + + mtd_part = mtd_parts; + names = (char *)&mtd_parts[num_parts]; + + strncpy(names, "myloader", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = 0; + mtd_part->size = offset; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + strncpy(names, "partition_table", PART_NAME_LEN); + mtd_part->name = names; + mtd_part->offset = offset; + mtd_part->size = blocklen; + mtd_part->mask_flags = MTD_WRITEABLE; + mtd_part++; + names += PART_NAME_LEN; + + for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { + part = &tab->partitions[i]; + + if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) + continue; + + if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) + strncpy(names, buf->names[i], PART_NAME_LEN); + else + snprintf(names, PART_NAME_LEN, "partition%d", i); + + mtd_part->offset = le32_to_cpu(part->addr); + mtd_part->size = le32_to_cpu(part->size); + mtd_part->name = names; + mtd_part++; + names += PART_NAME_LEN; + } + + *pparts = mtd_parts; + ret = num_parts; + + out_free_buf: + vfree(buf); + out: + return ret; +} + +static struct mtd_part_parser myloader_mtd_parser = { + .owner = THIS_MODULE, + .parse_fn = myloader_parse_partitions, + .name = "MyLoader", +}; + +static int __init myloader_mtd_parser_init(void) +{ + register_mtd_parser(&myloader_mtd_parser); + + return 0; +} + +static void __exit myloader_mtd_parser_exit(void) +{ + deregister_mtd_parser(&myloader_mtd_parser); +} + +module_init(myloader_mtd_parser_init); +module_exit(myloader_mtd_parser_exit); + +MODULE_AUTHOR("Gabor Juhos "); +MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/adm6996.c b/target/linux/generic/files-4.9/drivers/net/phy/adm6996.c new file mode 100644 index 0000000000..42928bab07 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/adm6996.c @@ -0,0 +1,1241 @@ +/* + * ADM6996 switch driver + * + * swconfig interface based on ar8216.c + * + * Copyright (c) 2008 Felix Fietkau + * VLAN support Copyright (c) 2010, 2011 Peter Lebbing + * Copyright (c) 2013 Hauke Mehrtens + * Copyright (c) 2014 Matti Laakso + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/*#define DEBUG 1*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "adm6996.h" + +MODULE_DESCRIPTION("Infineon ADM6996 Switch"); +MODULE_AUTHOR("Felix Fietkau, Peter Lebbing "); +MODULE_LICENSE("GPL"); + +static const char * const adm6996_model_name[] = +{ + NULL, + "ADM6996FC", + "ADM6996M", + "ADM6996L" +}; + +struct adm6996_mib_desc { + unsigned int offset; + const char *name; +}; + +struct adm6996_priv { + struct switch_dev dev; + void *priv; + + u8 eecs; + u8 eesk; + u8 eedi; + + enum adm6996_model model; + + bool enable_vlan; + bool vlan_enabled; /* Current hardware state */ + +#ifdef DEBUG + u16 addr; /* Debugging: register address to operate on */ +#endif + + u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ + u8 tagged_ports; + + u16 vlan_id[ADM_NUM_VLANS]; + u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ + u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ + + struct mutex mib_lock; + char buf[2048]; + + struct mutex reg_mutex; + + /* use abstraction for regops, we want to add gpio support in the future */ + u16 (*read)(struct adm6996_priv *priv, enum admreg reg); + void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val); +}; + +#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) +#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) + +#define MIB_DESC(_o, _n) \ + { \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct adm6996_mib_desc adm6996_mibs[] = { + MIB_DESC(ADM_CL0, "RxPacket"), + MIB_DESC(ADM_CL6, "RxByte"), + MIB_DESC(ADM_CL12, "TxPacket"), + MIB_DESC(ADM_CL18, "TxByte"), + MIB_DESC(ADM_CL24, "Collision"), + MIB_DESC(ADM_CL30, "Error"), +}; + +#define ADM6996_MIB_RXB_ID 1 +#define ADM6996_MIB_TXB_ID 3 + +static inline u16 +r16(struct adm6996_priv *priv, enum admreg reg) +{ + return priv->read(priv, reg); +} + +static inline void +w16(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + priv->write(priv, reg, val); +} + +/* Minimum timing constants */ +#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */ +#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */ +#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */ + +static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + /* Bit bang from MSB to LSB */ + for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Output on rising edge */ + gpio_set_value(priv->eedi, (mask & buf[i])); + udelay(EEDI_SETUP_TIME); + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) +{ + int i, len = (bits + 7) / 8; + u8 mask; + + gpio_set_value(priv->eecs, cs); + udelay(EECK_EDGE_TIME); + + /* Byte assemble from MSB to LSB */ + for (i = 0; i < len; i++) { + u8 byte; + + /* Bit bang from MSB to LSB */ + for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { + u8 gp; + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + /* Input on rising edge */ + gp = gpio_get_value(priv->eedi); + if (gp) + byte |= mask; + + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + } + + *buf++ = byte; + } + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + + if (cs) + gpio_set_value(priv->eecs, 0); +} + +/* Advance clock(s) */ +static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) +{ + int i; + for (i = 0; i < clocks; i++) { + /* Clock high */ + gpio_set_value(priv->eesk, 1); + udelay(EECK_EDGE_TIME); + + /* Clock low */ + gpio_set_value(priv->eesk, 0); + udelay(EECK_EDGE_TIME); + } +} + +static u16 +adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) +{ + /* cmd: 01 10 T DD R RRRRRR */ + u8 bits[6] = { + 0xFF, 0xFF, 0xFF, 0xFF, + (0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), + ((reg&63)<<2) + }; + + u8 rbits[4]; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + adm6996_gpio_write(priv, 0, bits, 46); + gpio_direction_input(priv->eedi); + adm6996_gpio_adclk(priv, 2); + adm6996_gpio_read(priv, 0, rbits, 32); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + + /* EEPROM has 16-bit registers, but pumps out two registers in one request */ + return (reg & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); +} + +/* Write chip configuration register */ +/* Follow 93c66 timing and chip's min EEPROM timing requirement */ +static void +adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ + u8 bits[4] = { + (0x05 << 5) | (reg >> 3), + (reg << 5) | (u8)(val >> 11), + (u8)(val >> 3), + (u8)(val << 5) + }; + + /* Enable GPIO outputs with all pins to 0 */ + gpio_direction_output(priv->eecs, 0); + gpio_direction_output(priv->eesk, 0); + gpio_direction_output(priv->eedi, 0); + + /* Write cmd. Total 27 bits */ + adm6996_gpio_write(priv, 1, bits, 27); + + /* Extra clock(s) required per datasheet */ + adm6996_gpio_adclk(priv, 2); + + /* Disable GPIO outputs */ + gpio_direction_input(priv->eecs); + gpio_direction_input(priv->eesk); + gpio_direction_input(priv->eedi); +} + +static u16 +adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void +adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) +{ + struct phy_device *phydev = priv->priv; + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1) + return -EINVAL; + + priv->enable_vlan = val->value.i; + + return 0; +}; + +static int +adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->enable_vlan; + + return 0; +}; + +#ifdef DEBUG + +static int +adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 1023) + return -EINVAL; + + priv->addr = val->value.i; + + return 0; +}; + +static int +adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = priv->addr; + + return 0; +}; + +static int +adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + if (val->value.i > 65535) + return -EINVAL; + + w16(priv, priv->addr, val->value.i); + + return 0; +}; + +static int +adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + val->value.i = r16(priv, priv->addr); + + return 0; +}; + +#endif /* def DEBUG */ + +static int +adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_pvid port %d vlan %d\n", port, vlan); + + if (vlan > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->pvid[port] = vlan; + + return 0; +} + +static int +adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_pvid port %d\n", port); + *vlan = priv->pvid[port]; + + return 0; +} + +static int +adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i); + + if (val->value.i > ADM_VLAN_MAX_ID) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + + return 0; +}; + +static int +adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("get_vid port %d\n", val->port_vlan); + + val->value.i = priv->vlan_id[val->port_vlan]; + + return 0; +}; + +static int +adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + u8 tagged = priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("get_ports port_vlan %d\n", val->port_vlan); + + val->len = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + + return 0; +}; + +static int +adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + u8 *ports = &priv->vlan_table[val->port_vlan]; + u8 *tagged = &priv->vlan_tagged[val->port_vlan]; + int i; + + pr_devel("set_ports port_vlan %d ports", val->port_vlan); + + *ports = 0; + *tagged = 0; + + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + +#ifdef DEBUG + pr_cont(" %d%s", p->id, + ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : + "")); +#endif + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + *tagged |= (1 << p->id); + priv->tagged_ports |= (1 << p->id); + } + + *ports |= (1 << p->id); + } + +#ifdef DEBUG + pr_cont("\n"); +#endif + + return 0; +}; + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_enable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_OTBE_MASK); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg &= ~(ADM_IFNTE_MASK); + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg |= ADM_VID_CHECK_MASK; + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg |= ADM_NTTE; + reg &= ~(ADM_RVID1); + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + w16(priv, ADM_SYSC3, reg); +} + +static void +adm6996_enable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + + reg = r16(priv, ADM_SYSC3); + reg |= ADM_TBV; + reg |= ADM_MAC_CLONE; + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + reg = ADM_VLAN_FILT_MEMBER_MASK; + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } + + reg = r16(priv, ADM_OTBE_P2_PVID); + reg |= ADM_OTBE_MASK; + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = r16(priv, ADM_IFNTE); + reg |= ADM_IFNTE_MASK; + w16(priv, ADM_IFNTE, reg); + reg = r16(priv, ADM_VID_CHECK); + reg &= ~(ADM_VID_CHECK_MASK); + w16(priv, ADM_VID_CHECK, reg); + reg = r16(priv, ADM_SYSC0); + reg &= ~(ADM_NTTE); + reg |= ADM_RVID1; + w16(priv, ADM_SYSC0, reg); + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Disable VLANs + * + * Sets VLAN mapping for port-based VLAN with all ports connected to + * eachother (this is also the power-on default). + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_disable_vlan_6996l(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + w16(priv, ADM_VLAN_MAP(i), 0); + } + + reg = r16(priv, ADM_SYSC3); + reg &= ~(ADM_TBV); + reg &= ~(ADM_MAC_CLONE); + w16(priv, ADM_SYSC3, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_port_pvids(struct adm6996_priv *priv) +{ + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + reg = r16(priv, adm_portcfg[i]); + reg &= ~(ADM_PORTCFG_PVID_MASK); + reg |= ADM_PORTCFG_PVID(priv->pvid[i]); + if (priv->model == ADM6996L) { + if (priv->tagged_ports & (1 << i)) + reg |= (1 << 4); + else + reg &= ~(1 << 4); + } + w16(priv, adm_portcfg[i], reg); + } + + w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); + w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); + reg = r16(priv, ADM_OTBE_P2_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P2_PVID_VAL(priv->pvid[2]); + w16(priv, ADM_OTBE_P2_PVID, reg); + reg = ADM_P3_PVID_VAL(priv->pvid[3]); + reg |= ADM_P4_PVID_VAL(priv->pvid[4]); + w16(priv, ADM_P3_P4_PVID, reg); + reg = r16(priv, ADM_P5_PVID); + reg &= ~(ADM_P2_PVID_MASK); + reg |= ADM_P5_PVID_VAL(priv->pvid[5]); + w16(priv, ADM_P5_PVID, reg); +} + +/* + * Precondition: reg_mutex must be held + */ +static void +adm6996_apply_vlan_filters(struct adm6996_priv *priv) +{ + u8 ports, tagged; + u16 vid, reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + vid = priv->vlan_id[i]; + ports = priv->vlan_table[i]; + tagged = priv->vlan_tagged[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_FILT_H(i), 0); + w16(priv, ADM_VLAN_FILT_L(i), 0); + continue; + } + + reg = ADM_VLAN_FILT_MEMBER(ports); + reg |= ADM_VLAN_FILT_TAGGED(tagged); + w16(priv, ADM_VLAN_FILT_L(i), reg); + reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); + w16(priv, ADM_VLAN_FILT_H(i), reg); + } +} + +static void +adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) +{ + u8 ports; + u16 reg; + int i; + + for (i = 0; i < ADM_NUM_VLANS; i++) { + ports = priv->vlan_table[i]; + + if (ports == 0) { + /* Disable VLAN entry */ + w16(priv, ADM_VLAN_MAP(i), 0); + continue; + } else { + reg = ADM_VLAN_FILT(ports); + w16(priv, ADM_VLAN_MAP(i), reg); + } + } +} + +static int +adm6996_hw_apply(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("hw_apply\n"); + + mutex_lock(&priv->reg_mutex); + + if (!priv->enable_vlan) { + if (priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_disable_vlan_6996l(priv); + else + adm6996_disable_vlan(priv); + priv->vlan_enabled = 0; + } + goto out; + } + + if (!priv->vlan_enabled) { + if (priv->model == ADM6996L) + adm6996_enable_vlan_6996l(priv); + else + adm6996_enable_vlan(priv); + priv->vlan_enabled = 1; + } + + adm6996_apply_port_pvids(priv); + if (priv->model == ADM6996L) + adm6996_apply_vlan_filters_6996l(priv); + else + adm6996_apply_vlan_filters(priv); + +out: + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +/* + * Reset the switch + * + * The ADM6996 can't do a software-initiated reset, so we just initialise the + * registers we support in this driver. + * + * Precondition: reg_mutex must be held + */ +static void +adm6996_perform_reset (struct adm6996_priv *priv) +{ + int i; + + /* initialize port and vlan settings */ + for (i = 0; i < ADM_NUM_PORTS - 1; i++) { + w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT | + ADM_PORTCFG_PVID(0)); + } + w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); + + if (priv->model == ADM6996M || priv->model == ADM6996FC) { + /* reset all PHY ports */ + for (i = 0; i < ADM_PHY_PORTS; i++) { + w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); + } + } + + priv->enable_vlan = 0; + priv->vlan_enabled = 0; + + for (i = 0; i < ADM_NUM_PORTS; i++) { + priv->pvid[i] = 0; + } + + for (i = 0; i < ADM_NUM_VLANS; i++) { + priv->vlan_id[i] = i; + priv->vlan_table[i] = 0; + priv->vlan_tagged[i] = 0; + } + + if (priv->model == ADM6996M) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan(priv); + adm6996_apply_port_pvids(priv); + } else if (priv->model == ADM6996L) { + /* Clear VLAN priority map so prio's are unused */ + w16 (priv, ADM_VLAN_PRIOMAP, 0); + + adm6996_disable_vlan_6996l(priv); + adm6996_apply_port_pvids(priv); + } +} + +static int +adm6996_reset_switch(struct switch_dev *dev) +{ + struct adm6996_priv *priv = to_adm(dev); + + pr_devel("reset\n"); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int +adm6996_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct adm6996_priv *priv = to_adm(dev); + + u16 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + switch (port) { + case 0: + reg = r16(priv, ADM_PS0); + break; + case 1: + reg = r16(priv, ADM_PS0); + reg = reg >> 8; + break; + case 2: + reg = r16(priv, ADM_PS1); + break; + case 3: + reg = r16(priv, ADM_PS1); + reg = reg >> 8; + break; + case 4: + reg = r16(priv, ADM_PS1); + reg = reg >> 12; + break; + case 5: + reg = r16(priv, ADM_PS2); + /* Bits 0, 1, 3 and 4. */ + reg = (reg & 3) | ((reg & 24) >> 1); + break; + default: + return -EINVAL; + } + + link->link = reg & ADM_PS_LS; + if (!link->link) + return 0; + link->aneg = true; + link->duplex = reg & ADM_PS_DS; + link->tx_flow = reg & ADM_PS_FCS; + link->rx_flow = reg & ADM_PS_FCS; + if (reg & ADM_PS_SS) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +adm6996_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct adm6996_priv *priv = to_adm(dev); + int port; + char *buf = priv->buf; + int i, len = 0; + u32 reg = 0; + + port = val->port_vlan; + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "Port %d MIB counters\n", + port); + + for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { + reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %u\n", + adm6996_mibs[i].name, + reg); + } + + mutex_unlock(&priv->mib_lock); + + val->value.s = buf; + val->len = len; + + return 0; +} + +static int +adm6996_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + struct adm6996_priv *priv = to_adm(dev); + int id; + u32 reg = 0; + + if (port >= ADM_NUM_PORTS) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + + id = ADM6996_MIB_TXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->tx_bytes = reg; + + id = ADM6996_MIB_RXB_ID; + reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); + reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; + stats->rx_bytes = reg; + + mutex_unlock(&priv->mib_lock); + + return 0; +} + +static struct switch_attr adm6996_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLANs", + .set = adm6996_set_enable_vlan, + .get = adm6996_get_enable_vlan, + }, +#ifdef DEBUG + { + .type = SWITCH_TYPE_INT, + .name = "addr", + .description = + "Direct register access: set register address (0 - 1023)", + .set = adm6996_set_addr, + .get = adm6996_get_addr, + }, + { + .type = SWITCH_TYPE_INT, + .name = "data", + .description = + "Direct register access: read/write to register (0 - 65535)", + .set = adm6996_set_data, + .get = adm6996_get_data, + }, +#endif /* def DEBUG */ +}; + +static struct switch_attr adm6996_port[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = adm6996_sw_get_port_mib, + }, +}; + +static struct switch_attr adm6996_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID", + .set = adm6996_set_vid, + .get = adm6996_get_vid, + }, +}; + +static struct switch_dev_ops adm6996_ops = { + .attr_global = { + .attr = adm6996_globals, + .n_attr = ARRAY_SIZE(adm6996_globals), + }, + .attr_port = { + .attr = adm6996_port, + .n_attr = ARRAY_SIZE(adm6996_port), + }, + .attr_vlan = { + .attr = adm6996_vlan, + .n_attr = ARRAY_SIZE(adm6996_vlan), + }, + .get_port_pvid = adm6996_get_pvid, + .set_port_pvid = adm6996_set_pvid, + .get_vlan_ports = adm6996_get_ports, + .set_vlan_ports = adm6996_set_ports, + .apply_config = adm6996_hw_apply, + .reset_switch = adm6996_reset_switch, + .get_port_link = adm6996_get_port_link, + .get_port_stats = adm6996_get_port_stats, +}; + +static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) +{ + struct switch_dev *swdev; + u16 test, old; + + if (!priv->model) { + /* Detect type of chip */ + old = r16(priv, ADM_VID_CHECK); + test = old ^ (1 << 12); + w16(priv, ADM_VID_CHECK, test); + test ^= r16(priv, ADM_VID_CHECK); + if (test & (1 << 12)) { + /* + * Bit 12 of this register is read-only. + * This is the FC model. + */ + priv->model = ADM6996FC; + } else { + /* Bit 12 is read-write. This is the M model. */ + priv->model = ADM6996M; + w16(priv, ADM_VID_CHECK, old); + } + } + + swdev = &priv->dev; + swdev->name = (adm6996_model_name[priv->model]); + swdev->cpu_port = ADM_CPU_PORT; + swdev->ports = ADM_NUM_PORTS; + swdev->vlans = ADM_NUM_VLANS; + swdev->ops = &adm6996_ops; + swdev->alias = alias; + + /* The ADM6996L connected through GPIOs does not support any switch + status calls */ + if (priv->model == ADM6996L) { + adm6996_ops.attr_port.n_attr = 0; + adm6996_ops.get_port_link = NULL; + } + + pr_info ("%s: %s model PHY found.\n", alias, swdev->name); + + mutex_lock(&priv->reg_mutex); + adm6996_perform_reset (priv); + mutex_unlock(&priv->reg_mutex); + + if (priv->model == ADM6996M || priv->model == ADM6996L) { + return register_switch(swdev, netdev); + } + + return -ENODEV; +} + +static int adm6996_config_init(struct phy_device *pdev) +{ + struct adm6996_priv *priv; + int ret; + + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + + if (pdev->mdio.addr != 0) { + pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" + , pdev->attached_dev->name, pdev->mdio.addr); + return 0; + } + + priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + priv->priv = pdev; + priv->read = adm6996_read_mii_reg; + priv->write = adm6996_write_mii_reg; + + ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); + if (ret < 0) + return ret; + + pdev->priv = priv; + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* + * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 + */ +static int adm6996_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int adm6996_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* Our custom registers are at PHY addresses 0-10. Claim those. */ + if (dev->mdio.addr > 10) + return 0; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; + if (reg != ADM_SIG0_VAL) + return 0; + + reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; + if (reg != ADM_SIG1_VAL) + return 0; + + dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; + + return 0; +} + +static int adm6996_probe(struct phy_device *pdev) +{ + return 0; +} + +static void adm6996_remove(struct phy_device *pdev) +{ + struct adm6996_priv *priv = phy_to_adm(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); +} + +static int adm6996_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver adm6996_phy_driver = { + .name = "Infineon ADM6996", + .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = adm6996_probe, + .remove = adm6996_remove, + .config_init = &adm6996_config_init, + .config_aneg = &adm6996_config_aneg, + .read_status = &adm6996_read_status, + .soft_reset = adm6996_soft_reset, +}; + +static int adm6996_gpio_probe(struct platform_device *pdev) +{ + struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; + struct adm6996_priv *priv; + int ret; + + if (!pdata) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + + priv->eecs = pdata->eecs; + priv->eedi = pdata->eedi; + priv->eesk = pdata->eesk; + + priv->model = pdata->model; + priv->read = adm6996_read_gpio_reg; + priv->write = adm6996_write_gpio_reg; + + ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); + if (ret) + return ret; + ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); + if (ret) + return ret; + + ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); + if (ret < 0) + return ret; + + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int adm6996_gpio_remove(struct platform_device *pdev) +{ + struct adm6996_priv *priv = platform_get_drvdata(pdev); + + if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) + unregister_switch(&priv->dev); + + return 0; +} + +static struct platform_driver adm6996_gpio_driver = { + .probe = adm6996_gpio_probe, + .remove = adm6996_gpio_remove, + .driver = { + .name = "adm6996_gpio", + }, +}; + +static int __init adm6996_init(void) +{ + int err; + + phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); + err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); + if (err) + return err; + + err = platform_driver_register(&adm6996_gpio_driver); + if (err) + phy_driver_unregister(&adm6996_phy_driver); + + return err; +} + +static void __exit adm6996_exit(void) +{ + platform_driver_unregister(&adm6996_gpio_driver); + phy_driver_unregister(&adm6996_phy_driver); +} + +module_init(adm6996_init); +module_exit(adm6996_exit); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/adm6996.h b/target/linux/generic/files-4.9/drivers/net/phy/adm6996.h new file mode 100644 index 0000000000..6fd460a465 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/adm6996.h @@ -0,0 +1,186 @@ +/* + * ADM6996 switch driver + * + * Copyright (c) 2008 Felix Fietkau + * Copyright (c) 2010,2011 Peter Lebbing + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __ADM6996_H +#define __ADM6996_H + +/* + * ADM_PHY_PORTS: Number of ports with a PHY. + * We only control ports 0 to 3, because if 4 is connected, it is most likely + * not connected to the switch but to a separate MII and MAC for the WAN port. + */ +#define ADM_PHY_PORTS 4 +#define ADM_NUM_PORTS 6 +#define ADM_CPU_PORT 5 + +#define ADM_NUM_VLANS 16 +#define ADM_VLAN_MAX_ID 4094 + +enum admreg { + ADM_EEPROM_BASE = 0x0, + ADM_P0_CFG = ADM_EEPROM_BASE + 1, + ADM_P1_CFG = ADM_EEPROM_BASE + 3, + ADM_P2_CFG = ADM_EEPROM_BASE + 5, + ADM_P3_CFG = ADM_EEPROM_BASE + 7, + ADM_P4_CFG = ADM_EEPROM_BASE + 8, + ADM_P5_CFG = ADM_EEPROM_BASE + 9, + ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, + ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, + ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, + /* Input Force No Tag Enable */ + ADM_IFNTE = ADM_EEPROM_BASE + 0x20, + ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, + ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, + ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, + /* Output Tag Bypass Enable and P2 PVID */ + ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, + ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, + ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, + ADM_EEPROM_EXT_BASE = 0x40, +#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) +#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) +#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n) + ADM_COUNTER_BASE = 0xa0, + ADM_SIG0 = ADM_COUNTER_BASE + 0, + ADM_SIG1 = ADM_COUNTER_BASE + 1, + ADM_PS0 = ADM_COUNTER_BASE + 2, + ADM_PS1 = ADM_COUNTER_BASE + 3, + ADM_PS2 = ADM_COUNTER_BASE + 4, + ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */ + ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */ + ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */ + ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */ + ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */ + ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */ +#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2) + ADM_PHY_BASE = 0x200, +#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) +}; + +/* Chip identification patterns */ +#define ADM_SIG0_MASK 0xffff +#define ADM_SIG0_VAL 0x1023 +#define ADM_SIG1_MASK 0xffff +#define ADM_SIG1_VAL 0x0007 + +enum { + ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ + ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ + ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ + ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ + ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ + ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ + ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ + ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ + ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ + ADM_PHYCFG_INIT = ( + ADM_PHYCFG_RST | + ADM_PHYCFG_SPEED_100 | + ADM_PHYCFG_ANEN | + ADM_PHYCFG_ANEN_RST + ) +}; + +enum { + ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ + ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ + ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ + ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ + ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ + ADM_PORTCFG_PD = (1 << 5), /* Port disable */ + ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority + * 1 = TOS based priority */ + ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ + ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ + ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ + ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ + ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ + + ADM_PORTCFG_INIT = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_AN | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_DPLX | + ADM_PORTCFG_CAM + ), + ADM_PORTCFG_CPU = ( + ADM_PORTCFG_FC | + ADM_PORTCFG_SPEED_100 | + ADM_PORTCFG_OT | + ADM_PORTCFG_DPLX + ), +}; + +#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) +#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) +#define ADM_PORTCFG_PVID_MASK (0xf << 10) + +#define ADM_IFNTE_MASK (0x3f << 9) +#define ADM_VID_CHECK_MASK (0x3f << 6) + +#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) +#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) +#define ADM_P2_PVID_MASK 0xff + +#define ADM_OTBE(n) (((n) & 0x3f) << 8) +#define ADM_OTBE_MASK (0x3f << 8) + +/* ADM_SYSC0 */ +enum { + ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ + ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ +}; + +/* Tag Based VLAN in ADM_SYSC3 */ +#define ADM_MAC_CLONE BIT(4) +#define ADM_TBV BIT(5) + +static const u8 adm_portcfg[] = { + [0] = ADM_P0_CFG, + [1] = ADM_P1_CFG, + [2] = ADM_P2_CFG, + [3] = ADM_P3_CFG, + [4] = ADM_P4_CFG, + [5] = ADM_P5_CFG, +}; + +/* Fields in ADM_VLAN_FILT_L(x) */ +#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) +#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) +#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) +#define ADM_VLAN_FILT_MEMBER_MASK 0x3f +/* Fields in ADM_VLAN_FILT_H(x) */ +#define ADM_VLAN_FILT_VALID (1 << 15) +#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) + +/* Convert ports to a form for ADM6996L VLAN map */ +#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \ + ((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \ + ((ports & 0x10) << 3) | ((ports & 0x20) << 3)) + +/* Port status register */ +enum { + ADM_PS_LS = (1 << 0), /* Link status */ + ADM_PS_SS = (1 << 1), /* Speed status */ + ADM_PS_DS = (1 << 2), /* Duplex status */ + ADM_PS_FCS = (1 << 3) /* Flow control status */ +}; + +/* + * Split the register address in phy id and register + * it will get combined again by the mdio bus op + */ +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/ar8216.c b/target/linux/generic/files-4.9/drivers/net/phy/ar8216.c new file mode 100644 index 0000000000..7512ee1b43 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/ar8216.c @@ -0,0 +1,2313 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" + +extern const struct ar8xxx_chip ar8327_chip; +extern const struct ar8xxx_chip ar8337_chip; + +#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ + +#define MIB_DESC(_s , _o, _n) \ + { \ + .size = (_s), \ + .offset = (_o), \ + .name = (_n), \ + } + +static const struct ar8xxx_mib_desc ar8216_mibs[] = { + MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), +}; + +const struct ar8xxx_mib_desc ar8236_mibs[39] = { + MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), + MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), + MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), + MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), + MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), + MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), + MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), + MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), + MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), + MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), + MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), + MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), + MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), + MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), + MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), + MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), + MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), + MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), + MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), + MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), + MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), + MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), + MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), + MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), + MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), + MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), + MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), + MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), + MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), + MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), + MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), + MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), + MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), + MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), + MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), + MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), + MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), + MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), + MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), +}; + +static DEFINE_MUTEX(ar8xxx_dev_list_lock); +static LIST_HEAD(ar8xxx_dev_list); + +/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ +static int +ar8xxx_phy_poll_reset(struct mii_bus *bus) +{ + unsigned int sleep_msecs = 20; + int ret, elapsed, i; + + for (elapsed = sleep_msecs; elapsed <= 600; + elapsed += sleep_msecs) { + msleep(sleep_msecs); + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + ret = mdiobus_read(bus, i, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_RESET) + break; + if (i == AR8XXX_NUM_PHYS - 1) { + usleep_range(1000, 2000); + return 0; + } + } + } + return -ETIMEDOUT; +} + +static int +ar8xxx_phy_check_aneg(struct phy_device *phydev) +{ + int ret; + + if (phydev->autoneg != AUTONEG_ENABLE) + return 0; + /* + * BMCR_ANENABLE might have been cleared + * by phy_init_hw in certain kernel versions + * therefore check for it + */ + ret = phy_read(phydev, MII_BMCR); + if (ret < 0) + return ret; + if (ret & BMCR_ANENABLE) + return 0; + + dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); + ret |= BMCR_ANENABLE | BMCR_ANRESTART; + return phy_write(phydev, MII_BMCR, ret); +} + +void +ar8xxx_phy_init(struct ar8xxx_priv *priv) +{ + int i; + struct mii_bus *bus; + + bus = priv->mii_bus; + for (i = 0; i < AR8XXX_NUM_PHYS; i++) { + if (priv->chip->phy_fixup) + priv->chip->phy_fixup(priv, i); + + /* initialize the port itself */ + mdiobus_write(bus, i, MII_ADVERTISE, + ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); + if (ar8xxx_has_gige(priv)) + mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); + mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + } + + ar8xxx_phy_poll_reset(bus); +} + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = bus->read(bus, phy_id, regnum); + hi = bus->read(bus, phy_id, regnum + 1); + + return (hi << 16) | lo; +} + +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 lo, hi; + + lo = val & 0xffff; + hi = (u16) (val >> 16); + + if (priv->chip->mii_lo_first) + { + bus->write(bus, phy_id, regnum, lo); + bus->write(bus, phy_id, regnum + 1, hi); + } else { + bus->write(bus, phy_id, regnum + 1, hi); + bus->write(bus, phy_id, regnum, lo); + } +} + +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 val; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + + mutex_unlock(&bus->mdio_lock); + + return val; +} + +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); + + mutex_unlock(&bus->mdio_lock); +} + +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r1, r2, page; + u32 ret; + + split_addr((u32) reg, &r1, &r2, &page); + + mutex_lock(&bus->mdio_lock); + + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); + ret &= ~mask; + ret |= val; + ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); + + mutex_unlock(&bus->mdio_lock); + + return ret; +} + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); + bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); + mutex_unlock(&bus->mdio_lock); +} + +static inline void +ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) +{ + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); +} + +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) +{ + struct mii_bus *bus = priv->mii_bus; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); + mutex_unlock(&bus->mdio_lock); +} + +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) +{ + struct mii_bus *bus = priv->mii_bus; + u16 data; + + mutex_lock(&bus->mdio_lock); + ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); + data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); + mutex_unlock(&bus->mdio_lock); + + return data; +} + +static int +ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, + unsigned timeout) +{ + int i; + + for (i = 0; i < timeout; i++) { + u32 t; + + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + usleep_range(1000, 2000); + cond_resched(); + } + + return -ETIMEDOUT; +} + +static int +ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) +{ + unsigned mib_func = priv->chip->mib_func; + int ret; + + lockdep_assert_held(&priv->mib_lock); + + /* Capture the hardware statistics for all ports */ + ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); + + /* Wait for the capturing to complete. */ + ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); + if (ret) + goto out; + + ret = 0; + +out: + return ret; +} + +static int +ar8xxx_mib_capture(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); +} + +static int +ar8xxx_mib_flush(struct ar8xxx_priv *priv) +{ + return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); +} + +static void +ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) +{ + unsigned int base; + u64 *mib_stats; + int i; + + WARN_ON(port >= priv->dev.ports); + + lockdep_assert_held(&priv->mib_lock); + + base = priv->chip->reg_port_stats_start + + priv->chip->reg_port_stats_length * port; + + mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; + for (i = 0; i < priv->chip->num_mibs; i++) { + const struct ar8xxx_mib_desc *mib; + u64 t; + + mib = &priv->chip->mib_decs[i]; + t = ar8xxx_read(priv, base + mib->offset); + if (mib->size == 2) { + u64 hi; + + hi = ar8xxx_read(priv, base + mib->offset + 4); + t |= hi << 32; + } + + if (flush) + mib_stats[i] = 0; + else + mib_stats[i] += t; + cond_resched(); + } +} + +static void +ar8216_read_port_link(struct ar8xxx_priv *priv, int port, + struct switch_port_link *link) +{ + u32 status; + u32 speed; + + memset(link, '\0', sizeof(*link)); + + status = priv->chip->read_port_status(priv, port); + + link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); + if (link->aneg) { + link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); + } else { + link->link = true; + + if (priv->get_port_link) { + int err; + + err = priv->get_port_link(port); + if (err >= 0) + link->link = !!err; + } + } + + if (!link->link) + return; + + link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); + link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); + link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); + + if (link->aneg && link->duplex && priv->chip->read_port_eee_status) + link->eee = priv->chip->read_port_eee_status(priv, port); + + speed = (status & AR8216_PORT_STATUS_SPEED) >> + AR8216_PORT_STATUS_SPEED_S; + + switch (speed) { + case AR8216_PORT_SPEED_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case AR8216_PORT_SPEED_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case AR8216_PORT_SPEED_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } +} + +static struct sk_buff * +ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv = dev->phy_ptr; + unsigned char *buf; + + if (unlikely(!priv)) + goto error; + + if (!priv->vlan) + goto send; + + if (unlikely(skb_headroom(skb) < 2)) { + if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) + goto error; + } + + buf = skb_push(skb, 2); + buf[0] = 0x10; + buf[1] = 0x80; + +send: + return skb; + +error: + dev_kfree_skb_any(skb); + return NULL; +} + +static void +ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct ar8xxx_priv *priv; + unsigned char *buf; + int port, vlan; + + priv = dev->phy_ptr; + if (!priv) + return; + + /* don't strip the header if vlan mode is disabled */ + if (!priv->vlan) + return; + + /* strip header, get vlan id */ + buf = skb->data; + skb_pull(skb, 2); + + /* check for vlan header presence */ + if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) + return; + + port = buf[0] & 0x7; + + /* no need to fix up packets coming from a tagged source */ + if (priv->vlan_tagged & (1 << port)) + return; + + /* lookup port vid from local table, the switch passes an invalid vlan id */ + vlan = priv->vlan_id[priv->pvid[port]]; + + buf[14 + 2] &= 0xf0; + buf[14 + 2] |= vlan >> 8; + buf[15 + 2] = vlan & 0xff; +} + +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) +{ + int timeout = 20; + u32 t = 0; + + while (1) { + t = ar8xxx_read(priv, reg); + if ((t & mask) == val) + return 0; + + if (timeout-- <= 0) + break; + + udelay(10); + cond_resched(); + } + + pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", + (unsigned int) reg, t, mask, val); + return -ETIMEDOUT; +} + +static void +ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) + return; + if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { + val &= AR8216_VTUDATA_MEMBER; + val |= AR8216_VTUDATA_VALID; + ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); + } + op |= AR8216_VTU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_VTU, op); +} + +static void +ar8216_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); +} + +static void +ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + + op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); + ar8216_vtu_op(priv, op, port_mask); +} + +static int +ar8216_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | + AR8216_ATU_ACTIVE); + + return ret; +} + +static int +ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); + if (!ret) { + t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; + t |= AR8216_ATU_ACTIVE; + ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); + } + + return ret; +} + +static u32 +ar8216_read_port_status(struct ar8xxx_priv *priv, int port) +{ + return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); +} + +static void +ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 header; + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) + header = AR8216_PORT_CTRL_HEADER; + else + header = 0; + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | header | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), + AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | + AR8216_PORT_VLAN_DEFAULT_ID, + (members << AR8216_PORT_VLAN_DEST_PORTS_S) | + (ingress << AR8216_PORT_VLAN_MODE_S) | + (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +} + +static int +ar8216_hw_init(struct ar8xxx_priv *priv) +{ + if (priv->initialized) + return 0; + + ar8xxx_phy_init(priv); + + priv->initialized = true; + return 0; +} + +static void +ar8216_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8216_GCTRL_MTU, 1518 + 8 + 2); +} + +static void +ar8216_init_port(struct ar8xxx_priv *priv, int port) +{ + /* Enable port learning and tx */ + ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | + (4 << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); + + if (port == AR8216_PORT_CPU) { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_UP | + (ar8xxx_has_gige(priv) ? + AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | + AR8216_PORT_STATUS_TXMAC | + AR8216_PORT_STATUS_RXMAC | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | + (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | + AR8216_PORT_STATUS_DUPLEX); + } else { + ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), + AR8216_PORT_STATUS_LINK_AUTO); + } +} + +static void +ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8216: timeout waiting for atu to become ready\n"); +} + +static void ar8216_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_func0, r1_func1, r1_func2; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); + r2 |= 0x10; + + r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; + r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8216_wait_atu_ready(priv, r2, r1_func0); + + ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); + ar8xxx_mii_write32(priv, r2, r1_func1, 0); + ar8xxx_mii_write32(priv, r2, r1_func2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + t = ar8xxx_mii_read32(priv, r2, r1_func0); + t |= AR8216_ATU_ACTIVE; + ar8xxx_mii_write32(priv, r2, r1_func0, t); + ar8216_wait_atu_ready(priv, r2, r1_func0); + + val0 = ar8xxx_mii_read32(priv, r2, r1_func0); + val1 = ar8xxx_mii_read32(priv, r2, r1_func1); + val2 = ar8xxx_mii_read32(priv, r2, r1_func2); + + *status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; + if (!*status) + break; + + i = 0; + t = AR8216_ATU_PORT0; + while (!(val2 & t) && ++i < priv->dev.ports) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; + a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; + a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; + a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; + a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; + a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; + break; + } +} + +static void +ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 egress, ingress; + u32 pvid; + + if (priv->vlan) { + pvid = priv->vlan_id[priv->pvid[port]]; + if (priv->vlan_tagged & (1 << port)) + egress = AR8216_OUT_ADD_VLAN; + else + egress = AR8216_OUT_STRIP_VLAN; + ingress = AR8216_IN_SECURE; + } else { + pvid = port; + egress = AR8216_OUT_KEEP; + ingress = AR8216_IN_PORT_ONLY; + } + + ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | + AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | + AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, + AR8216_PORT_CTRL_LEARN | + (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | + (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), + AR8236_PORT_VLAN_DEFAULT_ID, + (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); + + ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), + AR8236_PORT_VLAN2_VLAN_MODE | + AR8236_PORT_VLAN2_MEMBER, + (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | + (members << AR8236_PORT_VLAN2_MEMBER_S)); +} + +static void +ar8236_init_globals(struct ar8xxx_priv *priv) +{ + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* enable cpu port to receive arp frames */ + ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, + AR8236_ATU_CTRL_RES); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, + AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +static int +ar8316_hw_init(struct ar8xxx_priv *priv) +{ + u32 val, newval; + + val = ar8xxx_read(priv, AR8316_REG_POSTRIP); + + if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + if (priv->port4_phy) { + /* value taken from Ubiquiti RouterStation Pro */ + newval = 0x81461bea; + pr_info("ar8316: Using port 4 as PHY\n"); + } else { + newval = 0x01261be2; + pr_info("ar8316: Using port 4 as switch port\n"); + } + } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { + /* value taken from AVM Fritz!Box 7390 sources */ + newval = 0x010e5b71; + } else { + /* no known value for phy interface */ + pr_err("ar8316: unsupported mii mode: %d.\n", + priv->phy->interface); + return -EINVAL; + } + + if (val == newval) + goto out; + + ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); + + if (priv->port4_phy && + priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { + /* work around for phy4 rgmii mode */ + ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); + /* rx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); + /* tx delay */ + ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); + msleep(1000); + } + + ar8xxx_phy_init(priv); + +out: + priv->initialized = true; + return 0; +} + +static void +ar8316_init_globals(struct ar8xxx_priv *priv) +{ + /* standard atheros magic */ + ar8xxx_write(priv, 0x38, 0xc000050e); + + /* enable cpu port to receive multicast and broadcast frames */ + ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, + AR8316_GCTRL_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, + (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | + AR8236_MIB_EN); +} + +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + priv->vlan = !!val->value.i; + return 0; +} + +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan; + return 0; +} + + +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + /* make sure no invalid PVIDs get set */ + + if (vlan < 0 || vlan >= dev->vlans || + port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (port < 0 || port >= AR8X16_MAX_PORTS) + return -EINVAL; + + *vlan = priv->pvid[port]; + return 0; +} + +static int +ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + ar8216_read_port_link(priv, port, link); + return 0; +} + +static int +ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports; + int i; + + if (val->port_vlan >= AR8X16_MAX_VLANS) + return -EINVAL; + + ports = priv->vlan_table[val->port_vlan]; + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + priv->vlan_tagged |= (1 << p->id); + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8216_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + for (port = 0; port < AR8216_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_RX); + + ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), + AR8216_PORT_CTRL_MIRROR_TX); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8216_NUM_PORTS || + priv->monitor_port >= AR8216_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, + AR8216_GLOBAL_CPUPORT_MIRROR_PORT, + (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_RX); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), + AR8216_PORT_CTRL_MIRROR_TX); +} + +static inline u32 +ar8xxx_age_time_val(int age_time) +{ + return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; +} + +static inline void +ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) +{ + u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); + ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); +} + +int +ar8xxx_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u8 portmask[AR8X16_MAX_PORTS]; + int i, j; + + mutex_lock(&priv->reg_mutex); + /* flush all vlan translation unit entries */ + priv->chip->vtu_flush(priv); + + memset(portmask, 0, sizeof(portmask)); + if (!priv->init) { + /* calculate the port destination masks and load vlans + * into the vlan translation unit */ + for (j = 0; j < AR8X16_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (!vp) + continue; + + for (i = 0; i < dev->ports; i++) { + u8 mask = (1 << i); + if (vp & mask) + portmask[i] |= vp & ~mask; + } + + chip->vtu_load_vlan(priv, priv->vlan_id[j], + priv->vlan_table[j]); + } + } else { + /* vlan disabled: + * isolate all ports, but connect them to the cpu port */ + for (i = 0; i < dev->ports; i++) { + if (i == AR8216_PORT_CPU) + continue; + + portmask[i] = 1 << AR8216_PORT_CPU; + portmask[AR8216_PORT_CPU] |= (1 << i); + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < dev->ports; i++) { + chip->setup_port(priv, i, portmask[i]); + } + + chip->set_mirror_regs(priv); + + /* set age time */ + if (chip->reg_arl_ctrl) + ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +int +ar8xxx_sw_reset_switch(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + int i; + + mutex_lock(&priv->reg_mutex); + memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - + offsetof(struct ar8xxx_priv, vlan)); + + for (i = 0; i < AR8X16_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + /* Configure all ports */ + for (i = 0; i < dev->ports; i++) + chip->init_port(priv, i); + + priv->mirror_rx = false; + priv->mirror_tx = false; + priv->source_port = 0; + priv->monitor_port = 0; + priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; + + chip->init_globals(priv); + chip->atu_flush(priv); + + mutex_unlock(&priv->reg_mutex); + + return chip->sw_hw_apply(dev); +} + +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + unsigned int len; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + mutex_lock(&priv->mib_lock); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + memset(priv->mib_stats, '\0', len); + ret = ar8xxx_mib_flush(priv); + if (ret) + goto unlock; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_rx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_rx; + return 0; +} + +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->mirror_tx = !!val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->mirror_tx; + return 0; +} + +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->monitor_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->monitor_port; + return 0; +} + +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + priv->source_port = val->value.i; + priv->chip->set_mirror_regs(priv); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->source_port; + return 0; +} + +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port; + int ret; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, true); + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +static void +ar8xxx_byte_to_str(char *buf, int len, u64 byte) +{ + unsigned long b; + const char *unit; + + if (byte >= 0x40000000) { /* 1 GiB */ + b = byte * 10 / 0x40000000; + unit = "GiB"; + } else if (byte >= 0x100000) { /* 1 MiB */ + b = byte * 10 / 0x100000; + unit = "MiB"; + } else if (byte >= 0x400) { /* 1 KiB */ + b = byte * 10 / 0x400; + unit = "KiB"; + } else { + b = byte; + unit = "Byte"; + } + if (strcmp(unit, "Byte")) + snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); + else + snprintf(buf, len, "%lu %s", b, unit); +} + +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8xxx_chip *chip = priv->chip; + u64 *mib_stats, mib_data; + unsigned int port; + int ret; + char *buf = priv->buf; + char buf1[64]; + const char *mib_name; + int i, len = 0; + bool mib_stats_empty = true; + + if (!ar8xxx_has_mib_counters(priv)) + return -EOPNOTSUPP; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->mib_lock); + ret = ar8xxx_mib_capture(priv); + if (ret) + goto unlock; + + ar8xxx_mib_fetch_port_stat(priv, port, false); + + len += snprintf(buf + len, sizeof(priv->buf) - len, + "MIB counters\n"); + + mib_stats = &priv->mib_stats[port * chip->num_mibs]; + for (i = 0; i < chip->num_mibs; i++) { + mib_name = chip->mib_decs[i].name; + mib_data = mib_stats[i]; + len += snprintf(buf + len, sizeof(priv->buf) - len, + "%-12s: %llu\n", mib_name, mib_data); + if ((!strcmp(mib_name, "TxByte") || + !strcmp(mib_name, "RxGoodByte")) && + mib_data >= 1024) { + ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); + --len; /* discard newline at the end of buf */ + len += snprintf(buf + len, sizeof(priv->buf) - len, + " (%s)\n", buf1); + } + if (mib_stats_empty && mib_data) + mib_stats_empty = false; + } + + if (mib_stats_empty) + len = snprintf(buf, sizeof(priv->buf), "No MIB data"); + + val->value.s = buf; + val->len = len; + + ret = 0; + +unlock: + mutex_unlock(&priv->mib_lock); + return ret; +} + +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int age_time = val->value.i; + u32 age_time_val; + + if (age_time < 0) + return -EINVAL; + + age_time_val = ar8xxx_age_time_val(age_time); + if (age_time_val == 0 || age_time_val > 0xffff) + return -EINVAL; + + priv->arl_age_time = age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + val->value.i = priv->arl_age_time; + return 0; +} + +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct mii_bus *bus = priv->mii_bus; + const struct ar8xxx_chip *chip = priv->chip; + char *buf = priv->arl_buf; + int i, j, k, len = 0; + struct arl_entry *a, *a1; + u32 status; + + if (!chip->get_arl_entry) + return -EOPNOTSUPP; + + mutex_lock(&priv->reg_mutex); + mutex_lock(&bus->mdio_lock); + + chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); + + for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { + a = &priv->arl_table[i]; + duplicate: + chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); + + if (!status) + break; + + /* avoid duplicates + * ARL table can include multiple valid entries + * per MAC, just with differing status codes + */ + for (j = 0; j < i; ++j) { + a1 = &priv->arl_table[j]; + if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) + goto duplicate; + } + } + + mutex_unlock(&bus->mdio_lock); + + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "address resolution table\n"); + + if (i == AR8XXX_NUM_ARL_RECORDS) + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Too many entries found, displaying the first %d only!\n", + AR8XXX_NUM_ARL_RECORDS); + + for (j = 0; j < priv->dev.ports; ++j) { + for (k = 0; k < i; ++k) { + a = &priv->arl_table[k]; + if (a->port != j) + continue; + len += snprintf(buf + len, sizeof(priv->arl_buf) - len, + "Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + j, + a->mac[5], a->mac[4], a->mac[3], + a->mac[2], a->mac[1], a->mac[0]); + } + } + + val->value.s = buf; + val->len = len; + + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int ret; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush(priv); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port, ret; + + port = val->port_vlan; + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ret = priv->chip->atu_flush_port(priv, port); + mutex_unlock(&priv->reg_mutex); + + return ret; +} + +static const struct switch_attr ar8xxx_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8216_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, +}; + +const struct switch_attr ar8xxx_sw_attr_vlan[1] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = ar8xxx_sw_set_vid, + .get = ar8xxx_sw_get_vid, + .max = 4094, + }, +}; + +static const struct switch_dev_ops ar8xxx_sw_ops = { + .attr_global = { + .attr = ar8xxx_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), + }, + .attr_port = { + .attr = ar8xxx_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8xxx_sw_get_ports, + .set_vlan_ports = ar8xxx_sw_set_ports, + .apply_config = ar8xxx_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +static const struct ar8xxx_chip ar8216_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x19000, + .reg_port_stats_length = 0xa0, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8216", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8216_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8216_mibs), + .mib_decs = ar8216_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8236_chip = { + .caps = AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8236", + .ports = AR8216_NUM_PORTS, + .vlans = AR8216_NUM_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8216_hw_init, + .init_globals = ar8236_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8236_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static const struct ar8xxx_chip ar8316_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + + .reg_port_stats_start = 0x20000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8216_REG_ATU_CTRL, + + .name = "Atheros AR8316", + .ports = AR8216_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8xxx_sw_ops, + + .hw_init = ar8316_hw_init, + .init_globals = ar8316_init_globals, + .init_port = ar8216_init_port, + .setup_port = ar8216_setup_port, + .read_port_status = ar8216_read_port_status, + .atu_flush = ar8216_atu_flush, + .atu_flush_port = ar8216_atu_flush_port, + .vtu_flush = ar8216_vtu_flush, + .vtu_load_vlan = ar8216_vtu_load_vlan, + .set_mirror_regs = ar8216_set_mirror_regs, + .get_arl_entry = ar8216_get_arl_entry, + .sw_hw_apply = ar8xxx_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8216_REG_MIB_FUNC +}; + +static int +ar8xxx_id_chip(struct ar8xxx_priv *priv) +{ + u32 val; + u16 id; + int i; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { + u16 t; + + val = ar8xxx_read(priv, AR8216_REG_CTRL); + if (val == ~0) + return -ENODEV; + + t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); + if (t != id) + return -ENODEV; + } + + priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; + priv->chip_rev = (id & AR8216_CTRL_REVISION); + + switch (priv->chip_ver) { + case AR8XXX_VER_AR8216: + priv->chip = &ar8216_chip; + break; + case AR8XXX_VER_AR8236: + priv->chip = &ar8236_chip; + break; + case AR8XXX_VER_AR8316: + priv->chip = &ar8316_chip; + break; + case AR8XXX_VER_AR8327: + priv->chip = &ar8327_chip; + break; + case AR8XXX_VER_AR8337: + priv->chip = &ar8337_chip; + break; + default: + pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", + priv->chip_ver, priv->chip_rev); + + return -ENODEV; + } + + return 0; +} + +static void +ar8xxx_mib_work_func(struct work_struct *work) +{ + struct ar8xxx_priv *priv; + int err; + + priv = container_of(work, struct ar8xxx_priv, mib_work.work); + + mutex_lock(&priv->mib_lock); + + err = ar8xxx_mib_capture(priv); + if (err) + goto next_port; + + ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); + +next_port: + priv->mib_next_port++; + if (priv->mib_next_port >= priv->dev.ports) + priv->mib_next_port = 0; + + mutex_unlock(&priv->mib_lock); + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static int +ar8xxx_mib_init(struct ar8xxx_priv *priv) +{ + unsigned int len; + + if (!ar8xxx_has_mib_counters(priv)) + return 0; + + BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); + + len = priv->dev.ports * priv->chip->num_mibs * + sizeof(*priv->mib_stats); + priv->mib_stats = kzalloc(len, GFP_KERNEL); + + if (!priv->mib_stats) + return -ENOMEM; + + return 0; +} + +static void +ar8xxx_mib_start(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + schedule_delayed_work(&priv->mib_work, + msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); +} + +static void +ar8xxx_mib_stop(struct ar8xxx_priv *priv) +{ + if (!ar8xxx_has_mib_counters(priv)) + return; + + cancel_delayed_work_sync(&priv->mib_work); +} + +static struct ar8xxx_priv * +ar8xxx_create(void) +{ + struct ar8xxx_priv *priv; + + priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); + if (priv == NULL) + return NULL; + + mutex_init(&priv->reg_mutex); + mutex_init(&priv->mib_lock); + INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); + + return priv; +} + +static void +ar8xxx_free(struct ar8xxx_priv *priv) +{ + if (priv->chip && priv->chip->cleanup) + priv->chip->cleanup(priv); + + kfree(priv->chip_data); + kfree(priv->mib_stats); + kfree(priv); +} + +static int +ar8xxx_probe_switch(struct ar8xxx_priv *priv) +{ + const struct ar8xxx_chip *chip; + struct switch_dev *swdev; + int ret; + + ret = ar8xxx_id_chip(priv); + if (ret) + return ret; + + chip = priv->chip; + + swdev = &priv->dev; + swdev->cpu_port = AR8216_PORT_CPU; + swdev->name = chip->name; + swdev->vlans = chip->vlans; + swdev->ports = chip->ports; + swdev->ops = chip->swops; + + ret = ar8xxx_mib_init(priv); + if (ret) + return ret; + + return 0; +} + +static int +ar8xxx_start(struct ar8xxx_priv *priv) +{ + int ret; + + priv->init = true; + + ret = priv->chip->hw_init(priv); + if (ret) + return ret; + + ret = ar8xxx_sw_reset_switch(&priv->dev); + if (ret) + return ret; + + priv->init = false; + + ar8xxx_mib_start(priv); + + return 0; +} + +static int +ar8xxx_phy_config_init(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct net_device *dev = phydev->attached_dev; + int ret; + + if (WARN_ON(!priv)) + return -ENODEV; + + if (priv->chip->config_at_probe) + return ar8xxx_phy_check_aneg(phydev); + + priv->phy = phydev; + + if (phydev->mdio.addr != 0) { + if (chip_is_ar8316(priv)) { + /* switch device has been initialized, reinit */ + priv->dev.ports = (AR8216_NUM_PORTS - 1); + priv->initialized = false; + priv->port4_phy = true; + ar8316_hw_init(priv); + return 0; + } + + return 0; + } + + ret = ar8xxx_start(priv); + if (ret) + return ret; + + /* VID fixup only needed on ar8216 */ + if (chip_is_ar8216(priv)) { + dev->phy_ptr = priv; + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = ar8216_mangle_rx; + dev->eth_mangle_tx = ar8216_mangle_tx; + } + + return 0; +} + +static bool +ar8xxx_check_link_states(struct ar8xxx_priv *priv) +{ + bool link_new, changed = false; + u32 status; + int i; + + mutex_lock(&priv->reg_mutex); + + for (i = 0; i < priv->dev.ports; i++) { + status = priv->chip->read_port_status(priv, i); + link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); + if (link_new == priv->link_up[i]) + continue; + + priv->link_up[i] = link_new; + changed = true; + /* flush ARL entries for this port if it went down*/ + if (!link_new) + priv->chip->atu_flush_port(priv, i); + dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", + i, link_new ? "up" : "down"); + } + + mutex_unlock(&priv->reg_mutex); + + return changed; +} + +static int +ar8xxx_phy_read_status(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + struct switch_port_link link; + + /* check for switch port link changes */ + if (phydev->state == PHY_CHANGELINK) + ar8xxx_check_link_states(priv); + + if (phydev->mdio.addr != 0) + return genphy_read_status(phydev); + + ar8216_read_port_link(priv, phydev->mdio.addr, &link); + phydev->link = !!link.link; + if (!phydev->link) + return 0; + + switch (link.speed) { + case SWITCH_PORT_SPEED_10: + phydev->speed = SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + phydev->speed = SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + default: + phydev->speed = 0; + } + phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int +ar8xxx_phy_config_aneg(struct phy_device *phydev) +{ + if (phydev->mdio.addr == 0) + return 0; + + return genphy_config_aneg(phydev); +} + +static const u32 ar8xxx_phy_ids[] = { + 0x004dd033, + 0x004dd034, /* AR8327 */ + 0x004dd036, /* AR8337 */ + 0x004dd041, + 0x004dd042, + 0x004dd043, /* AR8236 */ +}; + +static bool +ar8xxx_phy_match(u32 phy_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) + if (phy_id == ar8xxx_phy_ids[i]) + return true; + + return false; +} + +static bool +ar8xxx_is_possible(struct mii_bus *bus) +{ + unsigned int i, found_phys = 0; + + for (i = 0; i < 5; i++) { + u32 phy_id; + + phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; + phy_id |= mdiobus_read(bus, i, MII_PHYSID2); + if (ar8xxx_phy_match(phy_id)) { + found_phys++; + } else if (phy_id) { + pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", + dev_name(&bus->dev), i, phy_id); + } + } + return !!found_phys; +} + +static int +ar8xxx_phy_probe(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv; + struct switch_dev *swdev; + int ret; + + /* skip PHYs at unused adresses */ + if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4) + return -ENODEV; + + if (!ar8xxx_is_possible(phydev->mdio.bus)) + return -ENODEV; + + mutex_lock(&ar8xxx_dev_list_lock); + list_for_each_entry(priv, &ar8xxx_dev_list, list) + if (priv->mii_bus == phydev->mdio.bus) + goto found; + + priv = ar8xxx_create(); + if (priv == NULL) { + ret = -ENOMEM; + goto unlock; + } + + priv->mii_bus = phydev->mdio.bus; + + ret = ar8xxx_probe_switch(priv); + if (ret) + goto free_priv; + + swdev = &priv->dev; + swdev->alias = dev_name(&priv->mii_bus->dev); + ret = register_switch(swdev, NULL); + if (ret) + goto free_priv; + + pr_info("%s: %s rev. %u switch registered on %s\n", + swdev->devname, swdev->name, priv->chip_rev, + dev_name(&priv->mii_bus->dev)); + + list_add(&priv->list, &ar8xxx_dev_list); + +found: + priv->use_count++; + + if (phydev->mdio.addr == 0) { + if (ar8xxx_has_gige(priv)) { + phydev->supported = SUPPORTED_1000baseT_Full; + phydev->advertising = ADVERTISED_1000baseT_Full; + } else { + phydev->supported = SUPPORTED_100baseT_Full; + phydev->advertising = ADVERTISED_100baseT_Full; + } + + if (priv->chip->config_at_probe) { + priv->phy = phydev; + + ret = ar8xxx_start(priv); + if (ret) + goto err_unregister_switch; + } + } else { + if (ar8xxx_has_gige(priv)) { + phydev->supported |= SUPPORTED_1000baseT_Full; + phydev->advertising |= ADVERTISED_1000baseT_Full; + } + } + + phydev->priv = priv; + + mutex_unlock(&ar8xxx_dev_list_lock); + + return 0; + +err_unregister_switch: + if (--priv->use_count) + goto unlock; + + unregister_switch(&priv->dev); + +free_priv: + ar8xxx_free(priv); +unlock: + mutex_unlock(&ar8xxx_dev_list_lock); + return ret; +} + +static void +ar8xxx_phy_detach(struct phy_device *phydev) +{ + struct net_device *dev = phydev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; +} + +static void +ar8xxx_phy_remove(struct phy_device *phydev) +{ + struct ar8xxx_priv *priv = phydev->priv; + + if (WARN_ON(!priv)) + return; + + phydev->priv = NULL; + + mutex_lock(&ar8xxx_dev_list_lock); + + if (--priv->use_count > 0) { + mutex_unlock(&ar8xxx_dev_list_lock); + return; + } + + list_del(&priv->list); + mutex_unlock(&ar8xxx_dev_list_lock); + + unregister_switch(&priv->dev); + ar8xxx_mib_stop(priv); + ar8xxx_free(priv); +} + +static int +ar8xxx_phy_soft_reset(struct phy_device *phydev) +{ + /* we don't need an extra reset */ + return 0; +} + +static struct phy_driver ar8xxx_phy_driver[] = { + { + .phy_id = 0x004d0000, + .name = "Atheros AR8216/AR8236/AR8316", + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = ar8xxx_phy_probe, + .remove = ar8xxx_phy_remove, + .detach = ar8xxx_phy_detach, + .config_init = ar8xxx_phy_config_init, + .config_aneg = ar8xxx_phy_config_aneg, + .read_status = ar8xxx_phy_read_status, + .soft_reset = ar8xxx_phy_soft_reset, + } +}; + +module_phy_driver(ar8xxx_phy_driver); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/ar8216.h b/target/linux/generic/files-4.9/drivers/net/phy/ar8216.h new file mode 100644 index 0000000000..ba0e0ddccd --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/ar8216.h @@ -0,0 +1,644 @@ +/* + * ar8216.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8216_H +#define __AR8216_H + +#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) + +#define AR8XXX_CAP_GIGE BIT(0) +#define AR8XXX_CAP_MIB_COUNTERS BIT(1) + +#define AR8XXX_NUM_PHYS 5 +#define AR8216_PORT_CPU 0 +#define AR8216_NUM_PORTS 6 +#define AR8216_NUM_VLANS 16 +#define AR8316_NUM_VLANS 4096 + +/* size of the vlan table */ +#define AR8X16_MAX_VLANS 128 +#define AR8X16_PROBE_RETRIES 10 +#define AR8X16_MAX_PORTS 8 + +#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7 +#define AR8XXX_DEFAULT_ARL_AGE_TIME 300 + +/* Atheros specific MII registers */ +#define MII_ATH_MMD_ADDR 0x0d +#define MII_ATH_MMD_DATA 0x0e +#define MII_ATH_DBG_ADDR 0x1d +#define MII_ATH_DBG_DATA 0x1e + +#define AR8216_REG_CTRL 0x0000 +#define AR8216_CTRL_REVISION BITS(0, 8) +#define AR8216_CTRL_REVISION_S 0 +#define AR8216_CTRL_VERSION BITS(8, 8) +#define AR8216_CTRL_VERSION_S 8 +#define AR8216_CTRL_RESET BIT(31) + +#define AR8216_REG_FLOOD_MASK 0x002C +#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) +#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) +#define AR8236_FM_CPU_BROADCAST_EN BIT(26) +#define AR8236_FM_CPU_BCAST_FWD_EN BIT(25) + +#define AR8216_REG_GLOBAL_CTRL 0x0030 +#define AR8216_GCTRL_MTU BITS(0, 11) +#define AR8236_GCTRL_MTU BITS(0, 14) +#define AR8316_GCTRL_MTU BITS(0, 14) + +#define AR8216_REG_VTU 0x0040 +#define AR8216_VTU_OP BITS(0, 3) +#define AR8216_VTU_OP_NOOP 0x0 +#define AR8216_VTU_OP_FLUSH 0x1 +#define AR8216_VTU_OP_LOAD 0x2 +#define AR8216_VTU_OP_PURGE 0x3 +#define AR8216_VTU_OP_REMOVE_PORT 0x4 +#define AR8216_VTU_ACTIVE BIT(3) +#define AR8216_VTU_FULL BIT(4) +#define AR8216_VTU_PORT BITS(8, 4) +#define AR8216_VTU_PORT_S 8 +#define AR8216_VTU_VID BITS(16, 12) +#define AR8216_VTU_VID_S 16 +#define AR8216_VTU_PRIO BITS(28, 3) +#define AR8216_VTU_PRIO_S 28 +#define AR8216_VTU_PRIO_EN BIT(31) + +#define AR8216_REG_VTU_DATA 0x0044 +#define AR8216_VTUDATA_MEMBER BITS(0, 10) +#define AR8236_VTUDATA_MEMBER BITS(0, 7) +#define AR8216_VTUDATA_VALID BIT(11) + +#define AR8216_REG_ATU_FUNC0 0x0050 +#define AR8216_ATU_OP BITS(0, 3) +#define AR8216_ATU_OP_NOOP 0x0 +#define AR8216_ATU_OP_FLUSH 0x1 +#define AR8216_ATU_OP_LOAD 0x2 +#define AR8216_ATU_OP_PURGE 0x3 +#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4 +#define AR8216_ATU_OP_FLUSH_PORT 0x5 +#define AR8216_ATU_OP_GET_NEXT 0x6 +#define AR8216_ATU_ACTIVE BIT(3) +#define AR8216_ATU_PORT_NUM BITS(8, 4) +#define AR8216_ATU_PORT_NUM_S 8 +#define AR8216_ATU_FULL_VIO BIT(12) +#define AR8216_ATU_ADDR5 BITS(16, 8) +#define AR8216_ATU_ADDR5_S 16 +#define AR8216_ATU_ADDR4 BITS(24, 8) +#define AR8216_ATU_ADDR4_S 24 + +#define AR8216_REG_ATU_FUNC1 0x0054 +#define AR8216_ATU_ADDR3 BITS(0, 8) +#define AR8216_ATU_ADDR3_S 0 +#define AR8216_ATU_ADDR2 BITS(8, 8) +#define AR8216_ATU_ADDR2_S 8 +#define AR8216_ATU_ADDR1 BITS(16, 8) +#define AR8216_ATU_ADDR1_S 16 +#define AR8216_ATU_ADDR0 BITS(24, 8) +#define AR8216_ATU_ADDR0_S 24 + +#define AR8216_REG_ATU_FUNC2 0x0058 +#define AR8216_ATU_PORTS BITS(0, 6) +#define AR8216_ATU_PORT0 BIT(0) +#define AR8216_ATU_PORT1 BIT(1) +#define AR8216_ATU_PORT2 BIT(2) +#define AR8216_ATU_PORT3 BIT(3) +#define AR8216_ATU_PORT4 BIT(4) +#define AR8216_ATU_PORT5 BIT(5) +#define AR8216_ATU_STATUS BITS(16, 4) +#define AR8216_ATU_STATUS_S 16 + +#define AR8216_REG_ATU_CTRL 0x005C +#define AR8216_ATU_CTRL_AGE_EN BIT(17) +#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) +#define AR8216_ATU_CTRL_AGE_TIME_S 0 +#define AR8236_ATU_CTRL_RES BIT(20) + +#define AR8216_REG_MIB_FUNC 0x0080 +#define AR8216_MIB_TIMER BITS(0, 16) +#define AR8216_MIB_AT_HALF_EN BIT(16) +#define AR8216_MIB_BUSY BIT(17) +#define AR8216_MIB_FUNC BITS(24, 3) +#define AR8216_MIB_FUNC_S 24 +#define AR8216_MIB_FUNC_NO_OP 0x0 +#define AR8216_MIB_FUNC_FLUSH 0x1 +#define AR8216_MIB_FUNC_CAPTURE 0x3 +#define AR8236_MIB_EN BIT(30) + +#define AR8216_REG_GLOBAL_CPUPORT 0x0078 +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) +#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 + +#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) +#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) +#define AR8216_PORT_STATUS_SPEED BITS(0,2) +#define AR8216_PORT_STATUS_SPEED_S 0 +#define AR8216_PORT_STATUS_TXMAC BIT(2) +#define AR8216_PORT_STATUS_RXMAC BIT(3) +#define AR8216_PORT_STATUS_TXFLOW BIT(4) +#define AR8216_PORT_STATUS_RXFLOW BIT(5) +#define AR8216_PORT_STATUS_DUPLEX BIT(6) +#define AR8216_PORT_STATUS_LINK_UP BIT(8) +#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) +#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) +#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12) + +#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) + +/* port forwarding state */ +#define AR8216_PORT_CTRL_STATE BITS(0, 3) +#define AR8216_PORT_CTRL_STATE_S 0 + +#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) + +/* egress 802.1q mode */ +#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) +#define AR8216_PORT_CTRL_VLAN_MODE_S 8 + +#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) +#define AR8216_PORT_CTRL_HEADER BIT(11) +#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) +#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) +#define AR8216_PORT_CTRL_LEARN BIT(14) +#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) +#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) + +#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) + +#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) +#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 + +#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) +#define AR8216_PORT_VLAN_DEST_PORTS_S 16 + +/* bit0 added to the priority field of egress frames */ +#define AR8216_PORT_VLAN_TX_PRIO BIT(27) + +/* port default priority */ +#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) +#define AR8216_PORT_VLAN_PRIORITY_S 28 + +/* ingress 802.1q mode */ +#define AR8216_PORT_VLAN_MODE BITS(30, 2) +#define AR8216_PORT_VLAN_MODE_S 30 + +#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) +#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) + +#define AR8216_STATS_RXBROAD 0x00 +#define AR8216_STATS_RXPAUSE 0x04 +#define AR8216_STATS_RXMULTI 0x08 +#define AR8216_STATS_RXFCSERR 0x0c +#define AR8216_STATS_RXALIGNERR 0x10 +#define AR8216_STATS_RXRUNT 0x14 +#define AR8216_STATS_RXFRAGMENT 0x18 +#define AR8216_STATS_RX64BYTE 0x1c +#define AR8216_STATS_RX128BYTE 0x20 +#define AR8216_STATS_RX256BYTE 0x24 +#define AR8216_STATS_RX512BYTE 0x28 +#define AR8216_STATS_RX1024BYTE 0x2c +#define AR8216_STATS_RXMAXBYTE 0x30 +#define AR8216_STATS_RXTOOLONG 0x34 +#define AR8216_STATS_RXGOODBYTE 0x38 +#define AR8216_STATS_RXBADBYTE 0x40 +#define AR8216_STATS_RXOVERFLOW 0x48 +#define AR8216_STATS_FILTERED 0x4c +#define AR8216_STATS_TXBROAD 0x50 +#define AR8216_STATS_TXPAUSE 0x54 +#define AR8216_STATS_TXMULTI 0x58 +#define AR8216_STATS_TXUNDERRUN 0x5c +#define AR8216_STATS_TX64BYTE 0x60 +#define AR8216_STATS_TX128BYTE 0x64 +#define AR8216_STATS_TX256BYTE 0x68 +#define AR8216_STATS_TX512BYTE 0x6c +#define AR8216_STATS_TX1024BYTE 0x70 +#define AR8216_STATS_TXMAXBYTE 0x74 +#define AR8216_STATS_TXOVERSIZE 0x78 +#define AR8216_STATS_TXBYTE 0x7c +#define AR8216_STATS_TXCOLLISION 0x84 +#define AR8216_STATS_TXABORTCOL 0x88 +#define AR8216_STATS_TXMULTICOL 0x8c +#define AR8216_STATS_TXSINGLECOL 0x90 +#define AR8216_STATS_TXEXCDEFER 0x94 +#define AR8216_STATS_TXDEFER 0x98 +#define AR8216_STATS_TXLATECOL 0x9c + +#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) +#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) +#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 +#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) +#define AR8236_PORT_VLAN_PRIORITY_S 28 + +#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) +#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) +#define AR8236_PORT_VLAN2_MEMBER_S 16 +#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) +#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) +#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 + +#define AR8236_STATS_RXBROAD 0x00 +#define AR8236_STATS_RXPAUSE 0x04 +#define AR8236_STATS_RXMULTI 0x08 +#define AR8236_STATS_RXFCSERR 0x0c +#define AR8236_STATS_RXALIGNERR 0x10 +#define AR8236_STATS_RXRUNT 0x14 +#define AR8236_STATS_RXFRAGMENT 0x18 +#define AR8236_STATS_RX64BYTE 0x1c +#define AR8236_STATS_RX128BYTE 0x20 +#define AR8236_STATS_RX256BYTE 0x24 +#define AR8236_STATS_RX512BYTE 0x28 +#define AR8236_STATS_RX1024BYTE 0x2c +#define AR8236_STATS_RX1518BYTE 0x30 +#define AR8236_STATS_RXMAXBYTE 0x34 +#define AR8236_STATS_RXTOOLONG 0x38 +#define AR8236_STATS_RXGOODBYTE 0x3c +#define AR8236_STATS_RXBADBYTE 0x44 +#define AR8236_STATS_RXOVERFLOW 0x4c +#define AR8236_STATS_FILTERED 0x50 +#define AR8236_STATS_TXBROAD 0x54 +#define AR8236_STATS_TXPAUSE 0x58 +#define AR8236_STATS_TXMULTI 0x5c +#define AR8236_STATS_TXUNDERRUN 0x60 +#define AR8236_STATS_TX64BYTE 0x64 +#define AR8236_STATS_TX128BYTE 0x68 +#define AR8236_STATS_TX256BYTE 0x6c +#define AR8236_STATS_TX512BYTE 0x70 +#define AR8236_STATS_TX1024BYTE 0x74 +#define AR8236_STATS_TX1518BYTE 0x78 +#define AR8236_STATS_TXMAXBYTE 0x7c +#define AR8236_STATS_TXOVERSIZE 0x80 +#define AR8236_STATS_TXBYTE 0x84 +#define AR8236_STATS_TXCOLLISION 0x8c +#define AR8236_STATS_TXABORTCOL 0x90 +#define AR8236_STATS_TXMULTICOL 0x94 +#define AR8236_STATS_TXSINGLECOL 0x98 +#define AR8236_STATS_TXEXCDEFER 0x9c +#define AR8236_STATS_TXDEFER 0xa0 +#define AR8236_STATS_TXLATECOL 0xa4 + +#define AR8316_REG_POSTRIP 0x0008 +#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) +#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) +#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) +#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) +#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) +#define AR8316_POSTRIP_RTL_MODE BIT(5) +#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) +#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) +#define AR8316_POSTRIP_SERDES_EN BIT(8) +#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) +#define AR8316_POSTRIP_GATE_25M_EN BIT(10) +#define AR8316_POSTRIP_SEL_CLK25M BIT(11) +#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) +#define AR8316_POSTRIP_DBG_MODE_I BIT(13) +#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) +#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) +#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) +#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) +#define AR8316_POSTRIP_MAN_EN BIT(18) +#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) +#define AR8316_POSTRIP_LPW_EXIT BIT(20) +#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) +#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) +#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) +#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) +#define AR8316_POSTRIP_SPI_EN BIT(25) +#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) +#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) + +/* port speed */ +enum { + AR8216_PORT_SPEED_10M = 0, + AR8216_PORT_SPEED_100M = 1, + AR8216_PORT_SPEED_1000M = 2, + AR8216_PORT_SPEED_ERR = 3, +}; + +/* ingress 802.1q mode */ +enum { + AR8216_IN_PORT_ONLY = 0, + AR8216_IN_PORT_FALLBACK = 1, + AR8216_IN_VLAN_ONLY = 2, + AR8216_IN_SECURE = 3 +}; + +/* egress 802.1q mode */ +enum { + AR8216_OUT_KEEP = 0, + AR8216_OUT_STRIP_VLAN = 1, + AR8216_OUT_ADD_VLAN = 2 +}; + +/* port forwarding state */ +enum { + AR8216_PORT_STATE_DISABLED = 0, + AR8216_PORT_STATE_BLOCK = 1, + AR8216_PORT_STATE_LISTEN = 2, + AR8216_PORT_STATE_LEARN = 3, + AR8216_PORT_STATE_FORWARD = 4 +}; + +enum { + AR8XXX_VER_AR8216 = 0x01, + AR8XXX_VER_AR8236 = 0x03, + AR8XXX_VER_AR8316 = 0x10, + AR8XXX_VER_AR8327 = 0x12, + AR8XXX_VER_AR8337 = 0x13, +}; + +#define AR8XXX_NUM_ARL_RECORDS 100 + +enum arl_op { + AR8XXX_ARL_INITIALIZE, + AR8XXX_ARL_GET_NEXT +}; + +struct arl_entry { + u8 port; + u8 mac[6]; +}; + +struct ar8xxx_priv; + +struct ar8xxx_mib_desc { + unsigned int size; + unsigned int offset; + const char *name; +}; + +struct ar8xxx_chip { + unsigned long caps; + bool config_at_probe; + bool mii_lo_first; + + /* parameters to calculate REG_PORT_STATS_BASE */ + unsigned reg_port_stats_start; + unsigned reg_port_stats_length; + + unsigned reg_arl_ctrl; + + int (*hw_init)(struct ar8xxx_priv *priv); + void (*cleanup)(struct ar8xxx_priv *priv); + + const char *name; + int vlans; + int ports; + const struct switch_dev_ops *swops; + + void (*init_globals)(struct ar8xxx_priv *priv); + void (*init_port)(struct ar8xxx_priv *priv, int port); + void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members); + u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); + u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port); + int (*atu_flush)(struct ar8xxx_priv *priv); + int (*atu_flush_port)(struct ar8xxx_priv *priv, int port); + void (*vtu_flush)(struct ar8xxx_priv *priv); + void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); + void (*phy_fixup)(struct ar8xxx_priv *priv, int phy); + void (*set_mirror_regs)(struct ar8xxx_priv *priv); + void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a, + u32 *status, enum arl_op op); + int (*sw_hw_apply)(struct switch_dev *dev); + + const struct ar8xxx_mib_desc *mib_decs; + unsigned num_mibs; + unsigned mib_func; +}; + +struct ar8xxx_priv { + struct switch_dev dev; + struct mii_bus *mii_bus; + struct phy_device *phy; + + int (*get_port_link)(unsigned port); + + const struct net_device_ops *ndo_old; + struct net_device_ops ndo; + struct mutex reg_mutex; + u8 chip_ver; + u8 chip_rev; + const struct ar8xxx_chip *chip; + void *chip_data; + bool initialized; + bool port4_phy; + char buf[2048]; + struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS]; + char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256]; + bool link_up[AR8X16_MAX_PORTS]; + + bool init; + + struct mutex mib_lock; + struct delayed_work mib_work; + int mib_next_port; + u64 *mib_stats; + + struct list_head list; + unsigned int use_count; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[AR8X16_MAX_VLANS]; + u8 vlan_table[AR8X16_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[AR8X16_MAX_PORTS]; + int arl_age_time; + + /* mirroring */ + bool mirror_rx; + bool mirror_tx; + int source_port; + int monitor_port; + u8 port_vlan_prio[AR8X16_MAX_PORTS]; +}; + +u32 +ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum); +void +ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val); +u32 +ar8xxx_read(struct ar8xxx_priv *priv, int reg); +void +ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val); +u32 +ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +void +ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, + u16 dbg_addr, u16 dbg_data); +void +ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data); +u16 +ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg); +void +ar8xxx_phy_init(struct ar8xxx_priv *priv); +int +ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan); +int +ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan); +int +ar8xxx_sw_hw_apply(struct switch_dev *dev); +int +ar8xxx_sw_reset_switch(struct switch_dev *dev); +int +ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link); +int +ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_get_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int +ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); + +static inline struct ar8xxx_priv * +swdev_to_ar8xxx(struct switch_dev *swdev) +{ + return container_of(swdev, struct ar8xxx_priv, dev); +} + +static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_GIGE; +} + +static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) +{ + return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; +} + +static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8216; +} + +static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8236; +} + +static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8316; +} + +static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8327; +} + +static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) +{ + return priv->chip_ver == AR8XXX_VER_AR8337; +} + +static inline void +ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, 0, val); +} + +static inline void +ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val) +{ + ar8xxx_rmw(priv, reg, val, 0); +} + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ + regaddr >>= 1; + *r1 = regaddr & 0x1e; + + regaddr >>= 5; + *r2 = regaddr & 0x7; + + regaddr >>= 3; + *page = regaddr & 0x1ff; +} + +static inline void +wait_for_page_switch(void) +{ + udelay(5); +} + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/ar8327.c b/target/linux/generic/files-4.9/drivers/net/phy/ar8327.c new file mode 100644 index 0000000000..74f0a08d76 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/ar8327.c @@ -0,0 +1,1503 @@ +/* + * ar8327.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * Copyright (C) 2011-2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ar8216.h" +#include "ar8327.h" + +extern const struct ar8xxx_mib_desc ar8236_mibs[39]; +extern const struct switch_attr ar8xxx_sw_attr_vlan[1]; + +static u32 +ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) +{ + u32 t; + + if (!cfg) + return 0; + + t = 0; + switch (cfg->mode) { + case AR8327_PAD_NC: + break; + + case AR8327_PAD_MAC2MAC_MII: + t = AR8327_PAD_MAC_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2MAC_GMII: + t = AR8327_PAD_MAC_GMII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_SGMII: + t = AR8327_PAD_SGMII_EN; + + /* + * WAR for the QUalcomm Atheros AP136 board. + * It seems that RGMII TX/RX delay settings needs to be + * applied for SGMII mode as well, The ethernet is not + * reliable without this. + */ + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + + if (cfg->sgmii_delay_en) + t |= AR8327_PAD_SGMII_DELAY_EN; + + break; + + case AR8327_PAD_MAC2PHY_MII: + t = AR8327_PAD_PHY_MII_EN; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_MII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_MII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC2PHY_GMII: + t = AR8327_PAD_PHY_GMII_EN; + if (cfg->pipe_rxclk_sel) + t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; + if (cfg->rxclk_sel) + t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; + if (cfg->txclk_sel) + t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; + break; + + case AR8327_PAD_MAC_RGMII: + t = AR8327_PAD_RGMII_EN; + t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; + t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; + if (cfg->rxclk_delay_en) + t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; + if (cfg->txclk_delay_en) + t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; + break; + + case AR8327_PAD_PHY_GMII: + t = AR8327_PAD_PHYX_GMII_EN; + break; + + case AR8327_PAD_PHY_RGMII: + t = AR8327_PAD_PHYX_RGMII_EN; + break; + + case AR8327_PAD_PHY_MII: + t = AR8327_PAD_PHYX_MII_EN; + break; + } + + return t; +} + +static void +ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) +{ + switch (priv->chip_rev) { + case 1: + /* For 100M waveform */ + ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); + /* Turn on Gigabit clock */ + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); + break; + + case 2: + ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); + /* fallthrough */ + case 4: + ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); + ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); + ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); + ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); + break; + } +} + +static u32 +ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) +{ + u32 t; + + if (!cfg->force_link) + return AR8216_PORT_STATUS_LINK_AUTO; + + t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; + t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; + t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; + t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; + + switch (cfg->speed) { + case AR8327_PORT_SPEED_10: + t |= AR8216_PORT_SPEED_10M; + break; + case AR8327_PORT_SPEED_100: + t |= AR8216_PORT_SPEED_100M; + break; + case AR8327_PORT_SPEED_1000: + t |= AR8216_PORT_SPEED_1000M; + break; + } + + return t; +} + +#define AR8327_LED_ENTRY(_num, _reg, _shift) \ + [_num] = { .reg = (_reg), .shift = (_shift) } + +static const struct ar8327_led_entry +ar8327_led_map[AR8327_NUM_LEDS] = { + AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), + + AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), + AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), + AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), + + AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), + AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), + AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), + + AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), + AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), + AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), + + AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), + AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), +}; + +static void +ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, + enum ar8327_led_pattern pattern) +{ + const struct ar8327_led_entry *entry; + + entry = &ar8327_led_map[led_num]; + ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), + (3 << entry->shift), pattern << entry->shift); +} + +static void +ar8327_led_work_func(struct work_struct *work) +{ + struct ar8327_led *aled; + u8 pattern; + + aled = container_of(work, struct ar8327_led, led_work); + + pattern = aled->pattern; + + ar8327_set_led_pattern(aled->sw_priv, aled->led_num, + pattern); +} + +static void +ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) +{ + if (aled->pattern == pattern) + return; + + aled->pattern = pattern; + schedule_work(&aled->led_work); +} + +static inline struct ar8327_led * +led_cdev_to_ar8327_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct ar8327_led, cdev); +} + +static int +ar8327_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + + if (*delay_on == 0 && *delay_off == 0) { + *delay_on = 125; + *delay_off = 125; + } + + if (*delay_on != 125 || *delay_off != 125) { + /* + * The hardware only supports blinking at 4Hz. Fall back + * to software implementation in other cases. + */ + return -EINVAL; + } + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); + + spin_unlock(&aled->lock); + + return 0; +} + +static void +ar8327_led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + bool active; + + active = (brightness != LED_OFF); + active ^= aled->active_low; + + pattern = (active) ? AR8327_LED_PATTERN_ON : + AR8327_LED_PATTERN_OFF; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = false; + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); +} + +static ssize_t +ar8327_led_enable_hw_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + ssize_t ret = 0; + + ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); + + return ret; +} + +static ssize_t +ar8327_led_enable_hw_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); + u8 pattern; + u8 value; + int ret; + + ret = kstrtou8(buf, 10, &value); + if (ret < 0) + return -EINVAL; + + spin_lock(&aled->lock); + + aled->enable_hw_mode = !!value; + if (aled->enable_hw_mode) + pattern = AR8327_LED_PATTERN_RULE; + else + pattern = AR8327_LED_PATTERN_OFF; + + ar8327_led_schedule_change(aled, pattern); + + spin_unlock(&aled->lock); + + return size; +} + +static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, + ar8327_led_enable_hw_mode_show, + ar8327_led_enable_hw_mode_store); + +static int +ar8327_led_register(struct ar8327_led *aled) +{ + int ret; + + ret = led_classdev_register(NULL, &aled->cdev); + if (ret < 0) + return ret; + + if (aled->mode == AR8327_LED_MODE_HW) { + ret = device_create_file(aled->cdev.dev, + &dev_attr_enable_hw_mode); + if (ret) + goto err_unregister; + } + + return 0; + +err_unregister: + led_classdev_unregister(&aled->cdev); + return ret; +} + +static void +ar8327_led_unregister(struct ar8327_led *aled) +{ + if (aled->mode == AR8327_LED_MODE_HW) + device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); + + led_classdev_unregister(&aled->cdev); + cancel_work_sync(&aled->led_work); +} + +static int +ar8327_led_create(struct ar8xxx_priv *priv, + const struct ar8327_led_info *led_info) +{ + struct ar8327_data *data = priv->chip_data; + struct ar8327_led *aled; + int ret; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return 0; + + if (!led_info->name) + return -EINVAL; + + if (led_info->led_num >= AR8327_NUM_LEDS) + return -EINVAL; + + aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, + GFP_KERNEL); + if (!aled) + return -ENOMEM; + + aled->sw_priv = priv; + aled->led_num = led_info->led_num; + aled->active_low = led_info->active_low; + aled->mode = led_info->mode; + + if (aled->mode == AR8327_LED_MODE_HW) + aled->enable_hw_mode = true; + + aled->name = (char *)(aled + 1); + strcpy(aled->name, led_info->name); + + aled->cdev.name = aled->name; + aled->cdev.brightness_set = ar8327_led_set_brightness; + aled->cdev.blink_set = ar8327_led_blink_set; + aled->cdev.default_trigger = led_info->default_trigger; + + spin_lock_init(&aled->lock); + mutex_init(&aled->mutex); + INIT_WORK(&aled->led_work, ar8327_led_work_func); + + ret = ar8327_led_register(aled); + if (ret) + goto err_free; + + data->leds[data->num_leds++] = aled; + + return 0; + +err_free: + kfree(aled); + return ret; +} + +static void +ar8327_led_destroy(struct ar8327_led *aled) +{ + ar8327_led_unregister(aled); + kfree(aled); +} + +static void +ar8327_leds_init(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + + if (aled->enable_hw_mode) + aled->pattern = AR8327_LED_PATTERN_RULE; + else + aled->pattern = AR8327_LED_PATTERN_OFF; + + ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); + } +} + +static void +ar8327_leds_cleanup(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + unsigned i; + + if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) + return; + + for (i = 0; i < data->num_leds; i++) { + struct ar8327_led *aled; + + aled = data->leds[i]; + ar8327_led_destroy(aled); + } + + kfree(data->leds); +} + +static int +ar8327_hw_config_pdata(struct ar8xxx_priv *priv, + struct ar8327_platform_data *pdata) +{ + struct ar8327_led_cfg *led_cfg; + struct ar8327_data *data = priv->chip_data; + u32 pos, new_pos; + u32 t; + + if (!pdata) + return -EINVAL; + + priv->get_port_link = pdata->get_port_link; + + data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); + data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); + + t = ar8327_get_pad_cfg(pdata->pad0_cfg); + if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) + t |= AR8337_PAD_MAC06_EXCHANGE_EN; + ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); + + t = ar8327_get_pad_cfg(pdata->pad5_cfg); + ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); + t = ar8327_get_pad_cfg(pdata->pad6_cfg); + ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); + + pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRIP); + new_pos = pos; + + led_cfg = pdata->led_cfg; + if (led_cfg) { + if (led_cfg->open_drain) + new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; + else + new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; + + ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); + ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); + ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); + ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); + + if (new_pos != pos) + new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; + } + + if (pdata->sgmii_cfg) { + t = pdata->sgmii_cfg->sgmii_ctrl; + if (priv->chip_rev == 1) + t |= AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX; + else + t &= ~(AR8327_SGMII_CTRL_EN_PLL | + AR8327_SGMII_CTRL_EN_RX | + AR8327_SGMII_CTRL_EN_TX); + + ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t); + + if (pdata->sgmii_cfg->serdes_aen) + new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; + else + new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; + } + + ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); + + if (pdata->leds && pdata->num_leds) { + int i; + + data->leds = kzalloc(pdata->num_leds * sizeof(void *), + GFP_KERNEL); + if (!data->leds) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) + ar8327_led_create(priv, &pdata->leds[i]); + } + + return 0; +} + +#ifdef CONFIG_OF +static int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + struct ar8327_data *data = priv->chip_data; + const __be32 *paddr; + int len; + int i; + + paddr = of_get_property(np, "qca,ar8327-initvals", &len); + if (!paddr || len < (2 * sizeof(*paddr))) + return -EINVAL; + + len /= sizeof(*paddr); + + for (i = 0; i < len - 1; i += 2) { + u32 reg; + u32 val; + + reg = be32_to_cpup(paddr + i); + val = be32_to_cpup(paddr + i + 1); + + switch (reg) { + case AR8327_REG_PORT_STATUS(0): + data->port0_status = val; + break; + case AR8327_REG_PORT_STATUS(6): + data->port6_status = val; + break; + default: + ar8xxx_write(priv, reg, val); + break; + } + } + + return 0; +} +#else +static inline int +ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) +{ + return -EINVAL; +} +#endif + +static int +ar8327_hw_init(struct ar8xxx_priv *priv) +{ + int ret; + + priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL); + if (!priv->chip_data) + return -ENOMEM; + + if (priv->phy->mdio.dev.of_node) + ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node); + else + ret = ar8327_hw_config_pdata(priv, + priv->phy->mdio.dev.platform_data); + + if (ret) + return ret; + + ar8327_leds_init(priv); + + ar8xxx_phy_init(priv); + + return 0; +} + +static void +ar8327_cleanup(struct ar8xxx_priv *priv) +{ + ar8327_leds_cleanup(priv); +} + +static void +ar8327_init_globals(struct ar8xxx_priv *priv) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + int i; + + /* enable CPU port and disable mirror port */ + t = AR8327_FWD_CTRL0_CPU_PORT_EN | + AR8327_FWD_CTRL0_MIRROR_PORT; + ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t); + + /* forward multicast and broadcast frames to CPU */ + t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | + (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); + ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t); + + /* enable jumbo frames */ + ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, + AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); + + /* Enable MIB counters */ + ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, + AR8327_MODULE_EN_MIB); + + /* Disable EEE on all phy's due to stability issues */ + for (i = 0; i < AR8XXX_NUM_PHYS; i++) + data->eee[i] = false; +} + +static void +ar8327_init_port(struct ar8xxx_priv *priv, int port) +{ + struct ar8327_data *data = priv->chip_data; + u32 t; + + if (port == AR8216_PORT_CPU) + t = data->port0_status; + else if (port == 6) + t = data->port6_status; + else + t = AR8216_PORT_STATUS_LINK_AUTO; + + if (port != AR8216_PORT_CPU && port != 6) { + /*hw limitation:if configure mac when there is traffic, + port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); + msleep(100); + t |= AR8216_PORT_STATUS_FLOW_CONTROL; + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } else { + ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); + } + + ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); + + t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = AR8327_PORT_LOOKUP_LEARN; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static u32 +ar8327_read_port_status(struct ar8xxx_priv *priv, int port) +{ + u32 t; + + t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); + /* map the flow control autoneg result bits to the flow control bits + * used in forced mode to allow ar8216_read_port_link detect + * flow control properly if autoneg is used + */ + if (t & AR8216_PORT_STATUS_LINK_UP && + t & AR8216_PORT_STATUS_LINK_AUTO) { + t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); + if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) + t |= AR8216_PORT_STATUS_TXFLOW; + if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) + t |= AR8216_PORT_STATUS_RXFLOW; + } + + return t; +} + +static u32 +ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) +{ + int phy; + u16 t; + + if (port >= priv->dev.ports) + return 0; + + if (port == 0 || port == 6) + return 0; + + phy = port - 1; + + /* EEE Ability Auto-negotiation Result */ + t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); + + return mmd_eee_adv_to_ethtool_adv_t(t); +} + +static int +ar8327_atu_flush(struct ar8xxx_priv *priv) +{ + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_OP_FLUSH | + AR8327_ATU_FUNC_BUSY); + + return ret; +} + +static int +ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) +{ + u32 t; + int ret; + + ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, + AR8327_ATU_FUNC_BUSY, 0); + if (!ret) { + t = (port << AR8327_ATU_PORT_NUM_S); + t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; + t |= AR8327_ATU_FUNC_BUSY; + ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); + } + + return ret; +} + +static int +ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) +{ + u32 fwd_ctrl, frame_ack; + + fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port)); + + return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & + fwd_ctrl) == fwd_ctrl && + (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & + frame_ack) == frame_ack; +} + +static void +ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) +{ + int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); + u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | + AR8327_FRAME_ACK_CTRL_IGMP_JOIN | + AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << + AR8327_FRAME_ACK_CTRL_S(port); + + if (enable) { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S); + ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); + } else { + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, + BIT(port) << AR8327_FWD_CTRL1_IGMP_S, + BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); + ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); + } +} + +static void +ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) +{ + if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, + AR8327_VTU_FUNC1_BUSY, 0)) + return; + + if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) + ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val); + + op |= AR8327_VTU_FUNC1_BUSY; + ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op); +} + +static void +ar8327_vtu_flush(struct ar8xxx_priv *priv) +{ + ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); +} + +static void +ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) +{ + u32 op; + u32 val; + int i; + + op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); + val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; + for (i = 0; i < AR8327_NUM_PORTS; i++) { + u32 mode; + + if ((port_mask & BIT(i)) == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_NOT; + else if (priv->vlan == 0) + mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; + else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid)) + mode = AR8327_VTU_FUNC0_EG_MODE_TAG; + else + mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; + + val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); + } + ar8327_vtu_op(priv, op, val); +} + +static void +ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members) +{ + u32 t; + u32 egress, ingress; + u32 pvid = priv->vlan_id[priv->pvid[port]]; + + if (priv->vlan) { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; + ingress = AR8216_IN_SECURE; + } else { + egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; + ingress = AR8216_IN_PORT_ONLY; + } + + t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; + t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; + if (priv->vlan && priv->port_vlan_prio[port]) { + u32 prio = priv->port_vlan_prio[port]; + + t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S; + t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S; + } + ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); + + t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; + t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S; + if (priv->vlan && priv->port_vlan_prio[port]) + t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP; + + ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); + + t = members; + t |= AR8327_PORT_LOOKUP_LEARN; + t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; + t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; + ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); +} + +static int +ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < dev->ports; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int +ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (val->port_vlan == priv->pvid[p->id]) { + priv->vlan_tagged |= (1 << p->id); + } + } else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + } + + *vt |= 1 << p->id; + } + return 0; +} + +static void +ar8327_set_mirror_regs(struct ar8xxx_priv *priv) +{ + int port; + + /* reset all mirror registers */ + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + for (port = 0; port < AR8327_NUM_PORTS; port++) { + ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); + } + + /* now enable mirroring if necessary */ + if (priv->source_port >= AR8327_NUM_PORTS || + priv->monitor_port >= AR8327_NUM_PORTS || + priv->source_port == priv->monitor_port) { + return; + } + + ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, + AR8327_FWD_CTRL0_MIRROR_PORT, + (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); + + if (priv->mirror_rx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), + AR8327_PORT_LOOKUP_ING_MIRROR_EN); + + if (priv->mirror_tx) + ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), + AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); +} + +static int +ar8327_sw_set_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + data->eee[phy] = !!(val->value.i); + + return 0; +} + +static int +ar8327_sw_get_eee(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int port = val->port_vlan; + int phy; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + + phy = port - 1; + + val->value.i = data->eee[phy]; + + return 0; +} + +static void +ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) +{ + int timeout = 20; + + while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) { + udelay(10); + cond_resched(); + } + + if (!timeout) + pr_err("ar8327: timeout waiting for atu to become ready\n"); +} + +static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, + struct arl_entry *a, u32 *status, enum arl_op op) +{ + struct mii_bus *bus = priv->mii_bus; + u16 r2, page; + u16 r1_data0, r1_data1, r1_data2, r1_func; + u32 t, val0, val1, val2; + int i; + + split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); + r2 |= 0x10; + + r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; + r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; + r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; + + switch (op) { + case AR8XXX_ARL_INITIALIZE: + /* all ATU registers are on the same page + * therefore set page only once + */ + bus->write(bus, 0x18, 0, page); + wait_for_page_switch(); + + ar8327_wait_atu_ready(priv, r2, r1_func); + + ar8xxx_mii_write32(priv, r2, r1_data0, 0); + ar8xxx_mii_write32(priv, r2, r1_data1, 0); + ar8xxx_mii_write32(priv, r2, r1_data2, 0); + break; + case AR8XXX_ARL_GET_NEXT: + ar8xxx_mii_write32(priv, r2, r1_func, + AR8327_ATU_FUNC_OP_GET_NEXT | + AR8327_ATU_FUNC_BUSY); + ar8327_wait_atu_ready(priv, r2, r1_func); + + val0 = ar8xxx_mii_read32(priv, r2, r1_data0); + val1 = ar8xxx_mii_read32(priv, r2, r1_data1); + val2 = ar8xxx_mii_read32(priv, r2, r1_data2); + + *status = val2 & AR8327_ATU_STATUS; + if (!*status) + break; + + i = 0; + t = AR8327_ATU_PORT0; + while (!(val1 & t) && ++i < AR8327_NUM_PORTS) + t <<= 1; + + a->port = i; + a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; + a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; + a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; + a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; + a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; + a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; + break; + } +} + +static int +ar8327_sw_hw_apply(struct switch_dev *dev) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + const struct ar8327_data *data = priv->chip_data; + int ret, i; + + ret = ar8xxx_sw_hw_apply(dev); + if (ret) + return ret; + + for (i=0; i < AR8XXX_NUM_PHYS; i++) { + if (data->eee[i]) + ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + else + ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL, + AR8327_EEE_CTRL_DISABLE_PHY(i)); + } + + return 0; +} + +int +ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + val->value.i = ar8327_get_port_igmp(priv, port); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + + mutex_lock(&priv->reg_mutex); + ar8327_set_port_igmp(priv, port, val->value.i); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_get_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || + !val->value.i) + break; + } + + return 0; +} + +int +ar8327_sw_set_igmp_snooping(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int port; + + for (port = 0; port < dev->ports; port++) { + val->port_vlan = port; + if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) + break; + } + + return 0; +} + +int +ar8327_sw_get_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + u32 val_reg; + + mutex_lock(&priv->reg_mutex); + val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); + val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +int +ar8327_sw_set_igmp_v3(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + + mutex_lock(&priv->reg_mutex); + if (val->value.i) + ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + else + ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, + AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); + mutex_unlock(&priv->reg_mutex); + + return 0; +} + +static int +ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + if (port >= dev->ports) + return -EINVAL; + if (port == 0 || port == 6) + return -EOPNOTSUPP; + if (val->value.i < 0 || val->value.i > 7) + return -EINVAL; + + priv->port_vlan_prio[port] = val->value.i; + + return 0; +} + +static int +ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); + int port = val->port_vlan; + + val->value.i = priv->port_vlan_prio[port]; + + return 0; +} + +static const struct switch_attr ar8327_sw_attr_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = ar8xxx_sw_set_vlan, + .get = ar8xxx_sw_get_vlan, + .max = 1 + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = ar8xxx_sw_set_reset_mibs, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = ar8xxx_sw_set_mirror_rx_enable, + .get = ar8xxx_sw_get_mirror_rx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = ar8xxx_sw_set_mirror_tx_enable, + .get = ar8xxx_sw_get_mirror_tx_enable, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = ar8xxx_sw_set_mirror_monitor_port, + .get = ar8xxx_sw_get_mirror_monitor_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = ar8xxx_sw_set_mirror_source_port, + .get = ar8xxx_sw_get_mirror_source_port, + .max = AR8327_NUM_PORTS - 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "arl_age_time", + .description = "ARL age time (secs)", + .set = ar8xxx_sw_set_arl_age_time, + .get = ar8xxx_sw_get_arl_age_time, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "arl_table", + .description = "Get ARL table", + .set = NULL, + .get = ar8xxx_sw_get_arl_table, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush ARL table", + .set = ar8xxx_sw_set_flush_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable IGMP Snooping", + .set = ar8327_sw_set_igmp_snooping, + .get = ar8327_sw_get_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_v3", + .description = "Enable IGMPv3 support", + .set = ar8327_sw_set_igmp_v3, + .get = ar8327_sw_get_igmp_v3, + .max = 1 + }, +}; + +static const struct switch_attr ar8327_sw_attr_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = ar8xxx_sw_set_port_reset_mib, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .set = NULL, + .get = ar8xxx_sw_get_port_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_eee", + .description = "Enable EEE PHY sleep mode", + .set = ar8327_sw_set_eee, + .get = ar8327_sw_get_eee, + .max = 1, + }, + { + .type = SWITCH_TYPE_NOVAL, + .name = "flush_arl_table", + .description = "Flush port's ARL table entries", + .set = ar8xxx_sw_set_flush_port_arl_table, + }, + { + .type = SWITCH_TYPE_INT, + .name = "igmp_snooping", + .description = "Enable port's IGMP Snooping", + .set = ar8327_sw_set_port_igmp_snooping, + .get = ar8327_sw_get_port_igmp_snooping, + .max = 1 + }, + { + .type = SWITCH_TYPE_INT, + .name = "vlan_prio", + .description = "Port VLAN default priority (VLAN PCP) (0-7)", + .set = ar8327_sw_set_port_vlan_prio, + .get = ar8327_sw_get_port_vlan_prio, + .max = 7, + }, +}; + +static const struct switch_dev_ops ar8327_sw_ops = { + .attr_global = { + .attr = ar8327_sw_attr_globals, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), + }, + .attr_port = { + .attr = ar8327_sw_attr_port, + .n_attr = ARRAY_SIZE(ar8327_sw_attr_port), + }, + .attr_vlan = { + .attr = ar8xxx_sw_attr_vlan, + .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), + }, + .get_port_pvid = ar8xxx_sw_get_pvid, + .set_port_pvid = ar8xxx_sw_set_pvid, + .get_vlan_ports = ar8327_sw_get_ports, + .set_vlan_ports = ar8327_sw_set_ports, + .apply_config = ar8327_sw_hw_apply, + .reset_switch = ar8xxx_sw_reset_switch, + .get_port_link = ar8xxx_sw_get_port_link, +/* The following op is disabled as it hogs the CPU and degrades performance. + An implementation has been attempted in 4d8a66d but reading MIB data is slow + on ar8xxx switches. + + The high CPU load has been traced down to the ar8xxx_reg_wait() call in + ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by + the request to update the MIB counter is cleared. */ +#if 0 + .get_port_stats = ar8xxx_sw_get_port_stats, +#endif +}; + +const struct ar8xxx_chip ar8327_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8327", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; + +const struct ar8xxx_chip ar8337_chip = { + .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, + .config_at_probe = true, + .mii_lo_first = true, + + .name = "Atheros AR8337", + .ports = AR8327_NUM_PORTS, + .vlans = AR8X16_MAX_VLANS, + .swops = &ar8327_sw_ops, + + .reg_port_stats_start = 0x1000, + .reg_port_stats_length = 0x100, + .reg_arl_ctrl = AR8327_REG_ARL_CTRL, + + .hw_init = ar8327_hw_init, + .cleanup = ar8327_cleanup, + .init_globals = ar8327_init_globals, + .init_port = ar8327_init_port, + .setup_port = ar8327_setup_port, + .read_port_status = ar8327_read_port_status, + .read_port_eee_status = ar8327_read_port_eee_status, + .atu_flush = ar8327_atu_flush, + .atu_flush_port = ar8327_atu_flush_port, + .vtu_flush = ar8327_vtu_flush, + .vtu_load_vlan = ar8327_vtu_load_vlan, + .phy_fixup = ar8327_phy_fixup, + .set_mirror_regs = ar8327_set_mirror_regs, + .get_arl_entry = ar8327_get_arl_entry, + .sw_hw_apply = ar8327_sw_hw_apply, + + .num_mibs = ARRAY_SIZE(ar8236_mibs), + .mib_decs = ar8236_mibs, + .mib_func = AR8327_REG_MIB_FUNC +}; diff --git a/target/linux/generic/files-4.9/drivers/net/phy/ar8327.h b/target/linux/generic/files-4.9/drivers/net/phy/ar8327.h new file mode 100644 index 0000000000..d53ef885b1 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/ar8327.h @@ -0,0 +1,325 @@ +/* + * ar8327.h: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __AR8327_H +#define __AR8327_H + +#define AR8327_NUM_PORTS 7 +#define AR8327_NUM_LEDS 15 +#define AR8327_PORTS_ALL 0x7f +#define AR8327_NUM_LED_CTRL_REGS 4 + +#define AR8327_REG_MASK 0x000 + +#define AR8327_REG_PAD0_MODE 0x004 +#define AR8327_REG_PAD5_MODE 0x008 +#define AR8327_REG_PAD6_MODE 0x00c +#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) +#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) +#define AR8327_PAD_MAC_MII_EN BIT(2) +#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) +#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) +#define AR8327_PAD_MAC_GMII_EN BIT(6) +#define AR8327_PAD_SGMII_EN BIT(7) +#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) +#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) +#define AR8327_PAD_PHY_MII_EN BIT(10) +#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) +#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) +#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) +#define AR8327_PAD_PHY_GMII_EN BIT(14) +#define AR8327_PAD_PHYX_GMII_EN BIT(16) +#define AR8327_PAD_PHYX_RGMII_EN BIT(17) +#define AR8327_PAD_PHYX_MII_EN BIT(18) +#define AR8327_PAD_SGMII_DELAY_EN BIT(19) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) +#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) +#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 +#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) +#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) +#define AR8327_PAD_RGMII_EN BIT(26) + +#define AR8327_REG_POWER_ON_STRIP 0x010 +#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) +#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) +#define AR8327_POWER_ON_STRIP_SERDES_AEN BIT(7) + +#define AR8327_REG_INT_STATUS0 0x020 +#define AR8327_INT0_VT_DONE BIT(20) + +#define AR8327_REG_INT_STATUS1 0x024 +#define AR8327_REG_INT_MASK0 0x028 +#define AR8327_REG_INT_MASK1 0x02c + +#define AR8327_REG_MODULE_EN 0x030 +#define AR8327_MODULE_EN_MIB BIT(0) + +#define AR8327_REG_MIB_FUNC 0x034 +#define AR8327_MIB_CPU_KEEP BIT(20) + +#define AR8327_REG_SERVICE_TAG 0x048 +#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4) +#define AR8327_REG_LED_CTRL0 0x050 +#define AR8327_REG_LED_CTRL1 0x054 +#define AR8327_REG_LED_CTRL2 0x058 +#define AR8327_REG_LED_CTRL3 0x05c +#define AR8327_REG_MAC_ADDR0 0x060 +#define AR8327_REG_MAC_ADDR1 0x064 + +#define AR8327_REG_MAX_FRAME_SIZE 0x078 +#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) + +#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) +#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10) +#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11) + +#define AR8327_REG_HEADER_CTRL 0x098 +#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) + +#define AR8327_REG_SGMII_CTRL 0x0e0 +#define AR8327_SGMII_CTRL_EN_PLL BIT(1) +#define AR8327_SGMII_CTRL_EN_RX BIT(2) +#define AR8327_SGMII_CTRL_EN_TX BIT(3) + +#define AR8327_REG_EEE_CTRL 0x100 +#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2) + +#define AR8327_REG_FRAME_ACK_CTRL0 0x210 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30) + +#define AR8327_REG_FRAME_ACK_CTRL1 0x214 +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18) +#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19) +#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22) +#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24) +#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25) + +#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4) +#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0) +#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1) +#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2) +#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3) +#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4) +#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5) +#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6) +#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8) + +#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) +#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3) +#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) +#define AR8327_PORT_VLAN0_DEF_SVID_S 0 +#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3) +#define AR8327_PORT_VLAN0_DEF_SPRI_S 13 +#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) +#define AR8327_PORT_VLAN0_DEF_CVID_S 16 +#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3) +#define AR8327_PORT_VLAN0_DEF_CPRI_S 29 + +#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) +#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4) +#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) +#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) +#define AR8327_PORT_VLAN1_OUT_MODE_S 12 +#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 +#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 +#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 + +#define AR8327_REG_ATU_DATA0 0x600 +#define AR8327_ATU_ADDR0 BITS(0, 8) +#define AR8327_ATU_ADDR0_S 0 +#define AR8327_ATU_ADDR1 BITS(8, 8) +#define AR8327_ATU_ADDR1_S 8 +#define AR8327_ATU_ADDR2 BITS(16, 8) +#define AR8327_ATU_ADDR2_S 16 +#define AR8327_ATU_ADDR3 BITS(24, 8) +#define AR8327_ATU_ADDR3_S 24 +#define AR8327_REG_ATU_DATA1 0x604 +#define AR8327_ATU_ADDR4 BITS(0, 8) +#define AR8327_ATU_ADDR4_S 0 +#define AR8327_ATU_ADDR5 BITS(8, 8) +#define AR8327_ATU_ADDR5_S 8 +#define AR8327_ATU_PORTS BITS(16, 7) +#define AR8327_ATU_PORT0 BIT(16) +#define AR8327_ATU_PORT1 BIT(17) +#define AR8327_ATU_PORT2 BIT(18) +#define AR8327_ATU_PORT3 BIT(19) +#define AR8327_ATU_PORT4 BIT(20) +#define AR8327_ATU_PORT5 BIT(21) +#define AR8327_ATU_PORT6 BIT(22) +#define AR8327_REG_ATU_DATA2 0x608 +#define AR8327_ATU_STATUS BITS(0, 4) + +#define AR8327_REG_ATU_FUNC 0x60c +#define AR8327_ATU_FUNC_OP BITS(0, 4) +#define AR8327_ATU_FUNC_OP_NOOP 0x0 +#define AR8327_ATU_FUNC_OP_FLUSH 0x1 +#define AR8327_ATU_FUNC_OP_LOAD 0x2 +#define AR8327_ATU_FUNC_OP_PURGE 0x3 +#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4 +#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5 +#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 +#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 +#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 +#define AR8327_ATU_PORT_NUM BITS(8, 4) +#define AR8327_ATU_PORT_NUM_S 8 +#define AR8327_ATU_FUNC_BUSY BIT(31) + +#define AR8327_REG_VTU_FUNC0 0x0610 +#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) +#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) +#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 +#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 +#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 +#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 +#define AR8327_VTU_FUNC0_IVL BIT(19) +#define AR8327_VTU_FUNC0_VALID BIT(20) + +#define AR8327_REG_VTU_FUNC1 0x0614 +#define AR8327_VTU_FUNC1_OP BITS(0, 3) +#define AR8327_VTU_FUNC1_OP_NOOP 0 +#define AR8327_VTU_FUNC1_OP_FLUSH 1 +#define AR8327_VTU_FUNC1_OP_LOAD 2 +#define AR8327_VTU_FUNC1_OP_PURGE 3 +#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 +#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 +#define AR8327_VTU_FUNC1_OP_GET_ONE 6 +#define AR8327_VTU_FUNC1_FULL BIT(4) +#define AR8327_VTU_FUNC1_PORT BIT(8, 4) +#define AR8327_VTU_FUNC1_PORT_S 8 +#define AR8327_VTU_FUNC1_VID BIT(16, 12) +#define AR8327_VTU_FUNC1_VID_S 16 +#define AR8327_VTU_FUNC1_BUSY BIT(31) + +#define AR8327_REG_ARL_CTRL 0x0618 + +#define AR8327_REG_FWD_CTRL0 0x620 +#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) +#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) +#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 + +#define AR8327_REG_FWD_CTRL1 0x624 +#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) +#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 +#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) +#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 +#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) +#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 +#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) +#define AR8327_FWD_CTRL1_IGMP_S 24 + +#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) +#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) +#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) +#define AR8327_PORT_LOOKUP_IN_MODE_S 8 +#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) +#define AR8327_PORT_LOOKUP_STATE_S 16 +#define AR8327_PORT_LOOKUP_LEARN BIT(20) +#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25) + +#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) + +#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) +#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) + +#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31) + +enum ar8327_led_pattern { + AR8327_LED_PATTERN_OFF = 0, + AR8327_LED_PATTERN_BLINK, + AR8327_LED_PATTERN_ON, + AR8327_LED_PATTERN_RULE, +}; + +struct ar8327_led_entry { + unsigned reg; + unsigned shift; +}; + +struct ar8327_led { + struct led_classdev cdev; + struct ar8xxx_priv *sw_priv; + + char *name; + bool active_low; + u8 led_num; + enum ar8327_led_mode mode; + + struct mutex mutex; + spinlock_t lock; + struct work_struct led_work; + bool enable_hw_mode; + enum ar8327_led_pattern pattern; +}; + +struct ar8327_data { + u32 port0_status; + u32 port6_status; + + struct ar8327_led **leds; + unsigned int num_leds; + + /* all fields below are cleared on reset */ + bool eee[AR8XXX_NUM_PHYS]; +}; + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/Kconfig b/target/linux/generic/files-4.9/drivers/net/phy/b53/Kconfig new file mode 100644 index 0000000000..08287e7adf --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/Kconfig @@ -0,0 +1,37 @@ +menuconfig SWCONFIG_B53 + tristate "Broadcom bcm53xx managed switch support" + depends on SWCONFIG + help + This driver adds support for Broadcom managed switch chips. It supports + BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX + integrated switches. + +config SWCONFIG_B53_SPI_DRIVER + tristate "B53 SPI connected switch driver" + depends on SWCONFIG_B53 && SPI + help + Select to enable support for registering switches configured through SPI. + +config SWCONFIG_B53_PHY_DRIVER + tristate "B53 MDIO connected switch driver" + depends on SWCONFIG_B53 + select SWCONFIG_B53_PHY_FIXUP + help + Select to enable support for registering switches configured through MDIO. + +config SWCONFIG_B53_MMAP_DRIVER + tristate "B53 MMAP connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped switches like the BCM63XX + integrated switches. + +config SWCONFIG_B53_SRAB_DRIVER + tristate "B53 SRAB connected switch driver" + depends on SWCONFIG_B53 + help + Select to enable support for memory-mapped Switch Register Access + Bridge Registers (SRAB) like it is found on the BCM53010 + +config SWCONFIG_B53_PHY_FIXUP + bool diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/Makefile b/target/linux/generic/files-4.9/drivers/net/phy/b53/Makefile new file mode 100644 index 0000000000..13ff366448 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_SWCONFIG_B53) += b53_common.o + +obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o + +obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o +obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o +obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o +obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o + +ccflags-y += -Werror diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_common.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_common.c new file mode 100644 index 0000000000..670588c84e --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_common.c @@ -0,0 +1,1722 @@ +/* + * B53 switch driver main logic + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "b53_regs.h" +#include "b53_priv.h" + +/* buffer size needed for displaying all MIBs with max'd values */ +#define B53_BUF_SIZE 1188 + +struct b53_mib_desc { + u8 size; + u8 offset; + const char *name; +}; + +/* BCM5365 MIB counters */ +static const struct b53_mib_desc b53_mibs_65[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { }, +}; + +#define B63XX_MIB_TXB_ID 0 /* TxOctets */ +#define B63XX_MIB_RXB_ID 14 /* RxOctets */ + +/* BCM63xx MIB counters */ +static const struct b53_mib_desc b53_mibs_63xx[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x0c, "TxQoSPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x3c, "TxQoSOctets" }, + { 8, 0x44, "RxOctets" }, + { 4, 0x4c, "RxUndersizePkts" }, + { 4, 0x50, "RxPausePkts" }, + { 4, 0x54, "Pkts64Octets" }, + { 4, 0x58, "Pkts65to127Octets" }, + { 4, 0x5c, "Pkts128to255Octets" }, + { 4, 0x60, "Pkts256to511Octets" }, + { 4, 0x64, "Pkts512to1023Octets" }, + { 4, 0x68, "Pkts1024to1522Octets" }, + { 4, 0x6c, "RxOversizePkts" }, + { 4, 0x70, "RxJabbers" }, + { 4, 0x74, "RxAlignmentErrors" }, + { 4, 0x78, "RxFCSErrors" }, + { 8, 0x7c, "RxGoodOctets" }, + { 4, 0x84, "RxDropPkts" }, + { 4, 0x88, "RxUnicastPkts" }, + { 4, 0x8c, "RxMulticastPkts" }, + { 4, 0x90, "RxBroadcastPkts" }, + { 4, 0x94, "RxSAChanges" }, + { 4, 0x98, "RxFragments" }, + { 4, 0xa0, "RxSymbolErrors" }, + { 4, 0xa4, "RxQoSPkts" }, + { 8, 0xa8, "RxQoSOctets" }, + { 4, 0xb0, "Pkts1523to2047Octets" }, + { 4, 0xb4, "Pkts2048to4095Octets" }, + { 4, 0xb8, "Pkts4096to8191Octets" }, + { 4, 0xbc, "Pkts8192to9728Octets" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +#define B53XX_MIB_TXB_ID 0 /* TxOctets */ +#define B53XX_MIB_RXB_ID 12 /* RxOctets */ + +/* MIB counters */ +static const struct b53_mib_desc b53_mibs[] = { + { 8, 0x00, "TxOctets" }, + { 4, 0x08, "TxDropPkts" }, + { 4, 0x10, "TxBroadcastPkts" }, + { 4, 0x14, "TxMulticastPkts" }, + { 4, 0x18, "TxUnicastPkts" }, + { 4, 0x1c, "TxCollisions" }, + { 4, 0x20, "TxSingleCollision" }, + { 4, 0x24, "TxMultipleCollision" }, + { 4, 0x28, "TxDeferredTransmit" }, + { 4, 0x2c, "TxLateCollision" }, + { 4, 0x30, "TxExcessiveCollision" }, + { 4, 0x38, "TxPausePkts" }, + { 8, 0x50, "RxOctets" }, + { 4, 0x58, "RxUndersizePkts" }, + { 4, 0x5c, "RxPausePkts" }, + { 4, 0x60, "Pkts64Octets" }, + { 4, 0x64, "Pkts65to127Octets" }, + { 4, 0x68, "Pkts128to255Octets" }, + { 4, 0x6c, "Pkts256to511Octets" }, + { 4, 0x70, "Pkts512to1023Octets" }, + { 4, 0x74, "Pkts1024to1522Octets" }, + { 4, 0x78, "RxOversizePkts" }, + { 4, 0x7c, "RxJabbers" }, + { 4, 0x80, "RxAlignmentErrors" }, + { 4, 0x84, "RxFCSErrors" }, + { 8, 0x88, "RxGoodOctets" }, + { 4, 0x90, "RxDropPkts" }, + { 4, 0x94, "RxUnicastPkts" }, + { 4, 0x98, "RxMulticastPkts" }, + { 4, 0x9c, "RxBroadcastPkts" }, + { 4, 0xa0, "RxSAChanges" }, + { 4, 0xa4, "RxFragments" }, + { 4, 0xa8, "RxJumboPkts" }, + { 4, 0xac, "RxSymbolErrors" }, + { 4, 0xc0, "RxDiscarded" }, + { } +}; + +static int b53_do_vlan_op(struct b53_device *dev, u8 op) +{ + unsigned int i; + + b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); + + for (i = 0; i < 10; i++) { + u8 vta; + + b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); + if (!(vta & VTA_START_CMD)) + return 0; + + usleep_range(100, 200); + } + + return -EIO; +} + +static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, + u16 untag) +{ + if (is5325(dev)) { + u32 entry = 0; + + if (members) { + entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | + members; + if (dev->core_rev >= 3) + entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; + else + entry |= VA_VALID_25; + } + + b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else if (is5365(dev)) { + u16 entry = 0; + + if (members) + entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | + members | VA_VALID_65; + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | + VTA_RW_STATE_WR | VTA_RW_OP_EN); + } else { + b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); + b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], + (untag << VTE_UNTAG_S) | members); + + b53_do_vlan_op(dev, VTA_CMD_WRITE); + } +} + +void b53_set_forwarding(struct b53_device *dev, int enable) +{ + u8 mgmt; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (enable) + mgmt |= SM_SW_FWD_EN; + else + mgmt &= ~SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static void b53_enable_vlan(struct b53_device *dev, int enable) +{ + u8 mgmt, vc0, vc1, vc4 = 0, vc5; + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); + + if (is5325(dev) || is5365(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); + } else if (is63xx(dev)) { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); + } else { + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); + } + + mgmt &= ~SM_SW_FWD_MODE; + + if (enable) { + vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; + vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; + vc5 |= VC5_DROP_VTABLE_MISS; + + if (is5325(dev)) + vc0 &= ~VC0_RESERVED_1; + + if (is5325(dev) || is5365(dev)) + vc1 |= VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) { + if (dev->allow_vid_4095) + vc5 |= VC5_VID_FFF_EN; + else + vc5 &= ~VC5_VID_FFF_EN; + } + } else { + vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); + vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); + vc4 &= ~VC4_ING_VID_CHECK_MASK; + vc5 &= ~VC5_DROP_VTABLE_MISS; + + if (is5325(dev) || is5365(dev)) + vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; + else + vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; + + if (is5325(dev) || is5365(dev)) + vc1 &= ~VC1_RX_MCST_TAG_EN; + + if (!is5325(dev) && !is5365(dev)) + vc5 &= ~VC5_VID_FFF_EN; + } + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); + + if (is5325(dev) || is5365(dev)) { + /* enable the high 8 bit vid check on 5325 */ + if (is5325(dev) && enable) + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, + VC3_HIGH_8BIT_EN); + else + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); + } else if (is63xx(dev)) { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); + } else { + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); + b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); + } + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); +} + +static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) +{ + u32 port_mask = 0; + u16 max_size = JMS_MIN_SIZE; + + if (is5325(dev) || is5365(dev)) + return -EINVAL; + + if (enable) { + port_mask = dev->enabled_ports; + max_size = JMS_MAX_SIZE; + if (allow_10_100) + port_mask |= JPM_10_100_JUMBO_EN; + } + + b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); + return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); +} + +static int b53_flush_arl(struct b53_device *dev) +{ + unsigned int i; + + b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); + + for (i = 0; i < 10; i++) { + u8 fast_age_ctrl; + + b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, + &fast_age_ctrl); + + if (!(fast_age_ctrl & FAST_AGE_DONE)) + return 0; + + mdelay(1); + } + + pr_warn("time out while flushing ARL\n"); + + return -EINVAL; +} + +static void b53_enable_ports(struct b53_device *dev) +{ + unsigned i; + + b53_for_each_port(dev, i) { + u8 port_ctrl; + u16 pvlan_mask; + + /* + * prevent leaking packets between wan and lan in unmanaged + * mode through port vlans. + */ + if (dev->enable_vlan || is_cpu_port(dev, i)) + pvlan_mask = 0x1ff; + else if (is531x5(dev) || is5301x(dev)) + /* BCM53115 may use a different port as cpu port */ + pvlan_mask = BIT(dev->sw_dev.cpu_port); + else + pvlan_mask = BIT(B53_CPU_PORT); + + /* BCM5325 CPU port is at 8 */ + if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) + i = B53_CPU_PORT; + + if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) + /* disable unused ports 6 & 7 */ + port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; + else if (i == B53_CPU_PORT) + port_ctrl = PORT_CTRL_RX_BCST_EN | + PORT_CTRL_RX_MCST_EN | + PORT_CTRL_RX_UCST_EN; + else + port_ctrl = 0; + + b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), + pvlan_mask); + + /* port state is handled by bcm63xx_enet driver */ + if (!is63xx(dev) && !(is5301x(dev) && i == 6)) + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), + port_ctrl); + } +} + +static void b53_enable_mib(struct b53_device *dev) +{ + u8 gc; + + b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); + + b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); +} + +static int b53_apply(struct b53_device *dev) +{ + int i; + + /* clear all vlan entries */ + if (is5325(dev) || is5365(dev)) { + for (i = 1; i < dev->sw_dev.vlans; i++) + b53_set_vlan_entry(dev, i, 0, 0); + } else { + b53_do_vlan_op(dev, VTA_CMD_CLEAR); + } + + b53_enable_vlan(dev, dev->enable_vlan); + + /* fill VLAN table */ + if (dev->enable_vlan) { + for (i = 0; i < dev->sw_dev.vlans; i++) { + struct b53_vlan *vlan = &dev->vlans[i]; + + if (!vlan->members) + continue; + + b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); + } + + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), + dev->ports[i].pvid); + } else { + b53_for_each_port(dev, i) + b53_write16(dev, B53_VLAN_PAGE, + B53_VLAN_PORT_DEF_TAG(i), 1); + + } + + b53_enable_ports(dev); + + if (!is5325(dev) && !is5365(dev)) + b53_set_jumbo(dev, dev->enable_jumbo, 1); + + return 0; +} + +static void b53_switch_reset_gpio(struct b53_device *dev) +{ + int gpio = dev->reset_gpio; + + if (gpio < 0) + return; + + /* + * Reset sequence: RESET low(50ms)->high(20ms) + */ + gpio_set_value(gpio, 0); + mdelay(50); + + gpio_set_value(gpio, 1); + mdelay(20); + + dev->current_page = 0xff; +} + +static int b53_configure_ports_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + u32 port_num; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + + for_each_available_child_of_node(dn, pn) { + struct device_node *fixed_link; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + fixed_link = of_get_child_by_name(pn, "fixed-link"); + if (fixed_link) { + u32 spd; + u8 po = GMII_PO_LINK; + int mode = of_get_phy_mode(pn); + + if (!of_property_read_u32(fixed_link, "speed", &spd)) { + switch (spd) { + case 10: + po |= GMII_PO_SPEED_10M; + break; + case 100: + po |= GMII_PO_SPEED_100M; + break; + case 2000: + if (is_imp_port(dev, port_num)) + po |= PORT_OVERRIDE_SPEED_2000M; + else + po |= GMII_PO_SPEED_2000M; + /* fall through */ + case 1000: + po |= GMII_PO_SPEED_1000M; + break; + } + } + + if (of_property_read_bool(fixed_link, "full-duplex")) + po |= PORT_OVERRIDE_FULL_DUPLEX; + if (of_property_read_bool(fixed_link, "pause")) + po |= GMII_PO_RX_FLOW; + if (of_property_read_bool(fixed_link, "asym-pause")) + po |= GMII_PO_TX_FLOW; + + if (is_imp_port(dev, port_num)) { + po |= PORT_OVERRIDE_EN; + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) + po |= PORT_OVERRIDE_RV_MII_25; + + b53_write8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, po); + + if (is5325(dev) && + mode == PHY_INTERFACE_MODE_REVMII) { + b53_read8(dev, B53_CTRL_PAGE, + B53_PORT_OVERRIDE_CTRL, &po); + if (!(po & PORT_OVERRIDE_RV_MII_25)) + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } else { + po |= GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, + B53_GMII_PORT_OVERRIDE_CTRL(port_num), + po); + } + } + } + + return 0; +} + +static int b53_configure_ports(struct b53_device *dev) +{ + u8 cpu_port = dev->sw_dev.cpu_port; + + /* configure MII port if necessary */ + if (is5325(dev)) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + /* reverse mii needs to be enabled */ + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_RV_MII_25); + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + + if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { + pr_err("Failed to enable reverse MII mode\n"); + return -EINVAL; + } + } + } else if (is531x5(dev) && cpu_port == B53_CPU_PORT) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override | PORT_OVERRIDE_EN | + PORT_OVERRIDE_LINK); + + /* BCM47189 has another interface connected to the port 5 */ + if (dev->enabled_ports & BIT(5)) { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } else if (is5301x(dev)) { + if (cpu_port == 8) { + u8 mii_port_override; + + b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + &mii_port_override); + mii_port_override |= PORT_OVERRIDE_LINK | + PORT_OVERRIDE_RX_FLOW | + PORT_OVERRIDE_TX_FLOW | + PORT_OVERRIDE_SPEED_2000M | + PORT_OVERRIDE_EN; + b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, + mii_port_override); + + /* TODO: Ports 5 & 7 require some extra handling */ + } else { + u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port); + u8 gmii_po; + + b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); + gmii_po |= GMII_PO_LINK | + GMII_PO_RX_FLOW | + GMII_PO_TX_FLOW | + GMII_PO_EN | + GMII_PO_SPEED_2000M; + b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); + } + } + + return 0; +} + +static int b53_switch_reset(struct b53_device *dev) +{ + int ret = 0; + u8 mgmt; + + b53_switch_reset_gpio(dev); + + if (is539x(dev)) { + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); + b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); + } + + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + mgmt &= ~SM_SW_FWD_MODE; + mgmt |= SM_SW_FWD_EN; + + b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); + b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); + + if (!(mgmt & SM_SW_FWD_EN)) { + pr_err("Failed to enable switch!\n"); + return -EINVAL; + } + } + + /* enable all ports */ + b53_enable_ports(dev); + + if (dev->dev->of_node) + ret = b53_configure_ports_of(dev); + else + ret = b53_configure_ports(dev); + + if (ret) + return ret; + + b53_enable_mib(dev); + + return b53_flush_arl(dev); +} + +/* + * Swconfig glue functions + */ + +static int b53_global_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_vlan; + + return 0; +} + +static int b53_global_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_vlan = val->value.i; + + return 0; +} + +static int b53_global_get_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->enable_jumbo; + + return 0; +} + +static int b53_global_set_jumbo_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->enable_jumbo = val->value.i; + + return 0; +} + +static int b53_global_get_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->value.i = priv->allow_vid_4095; + + return 0; +} + +static int b53_global_set_4095_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + priv->allow_vid_4095 = val->value.i; + + return 0; +} + +static int b53_global_get_ports(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", + priv->enabled_ports); + val->value.s = priv->buf; + + return 0; +} + +static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct b53_device *priv = sw_to_b53(dev); + + *val = priv->ports[port].pvid; + + return 0; +} + +static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (val > 15 && is5325(priv)) + return -EINVAL; + if (val == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + priv->ports[port].pvid = val; + + return 0; +} + +static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port = &val->value.ports[0]; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + val->len = 0; + + if (!vlan->members) + return 0; + + for (i = 0; i < dev->ports; i++) { + if (!(vlan->members & BIT(i))) + continue; + + + if (!(vlan->untag & BIT(i))) + port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); + else + port->flags = 0; + + port->id = i; + val->len++; + port++; + } + + return 0; +} + +static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + struct switch_port *port; + struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; + int i; + + /* only BCM5325 and BCM5365 supports VID 0 */ + if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) + return -EINVAL; + + /* VLAN 4095 needs special handling */ + if (val->port_vlan == 4095 && !priv->allow_vid_4095) + return -EINVAL; + + port = &val->value.ports[0]; + vlan->members = 0; + vlan->untag = 0; + for (i = 0; i < val->len; i++, port++) { + vlan->members |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { + vlan->untag |= BIT(port->id); + priv->ports[port->id].pvid = val->port_vlan; + }; + } + + /* ignore disabled ports */ + vlan->members &= priv->enabled_ports; + vlan->untag &= priv->enabled_ports; + + return 0; +} + +static int b53_port_get_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (is_cpu_port(priv, port)) { + link->link = 1; + link->duplex = 1; + link->speed = is5325(priv) || is5365(priv) ? + SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; + link->aneg = 0; + } else if (priv->enabled_ports & BIT(port)) { + u32 speed; + u16 lnk, duplex; + + b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); + b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); + + lnk = (lnk >> port) & 1; + duplex = (duplex >> port) & 1; + + if (is5325(priv) || is5365(priv)) { + u16 tmp; + + b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); + speed = SPEED_PORT_FE(tmp, port); + } else { + b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); + speed = SPEED_PORT_GE(speed, port); + } + + link->link = lnk; + if (lnk) { + link->duplex = duplex; + switch (speed) { + case SPEED_STAT_10M: + link->speed = SWITCH_PORT_SPEED_10; + break; + case SPEED_STAT_100M: + link->speed = SWITCH_PORT_SPEED_100; + break; + case SPEED_STAT_1000M: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + } + + link->aneg = 1; + } else { + link->link = 0; + } + + return 0; + +} + +static int b53_port_set_link(struct switch_dev *sw_dev, int port, + struct switch_port_link *link) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + + /* + * TODO: BCM63XX requires special handling as it can have external phys + * and ports might be GE or only FE + */ + if (is63xx(dev)) + return -ENOTSUPP; + + if (port == sw_dev->cpu_port) + return -EINVAL; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && + (is5325(dev) || is5365(dev))) + return -EINVAL; + + if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex) + return -EINVAL; + + return switch_generic_set_link(sw_dev, port, link); +} + +static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_read16) + return priv->ops->phy_read16(priv, addr, reg, value); + + return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value) +{ + struct b53_device *priv = sw_to_b53(dev); + + if (priv->ops->phy_write16) + return priv->ops->phy_write16(priv, addr, reg, value); + + return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value); +} + +static int b53_global_reset_switch(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* reset vlans */ + priv->enable_vlan = 0; + priv->enable_jumbo = 0; + priv->allow_vid_4095 = 0; + + memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans); + memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports); + + return b53_switch_reset(priv); +} + +static int b53_global_apply_config(struct switch_dev *dev) +{ + struct b53_device *priv = sw_to_b53(dev); + + /* disable switching */ + b53_set_forwarding(priv, 0); + + b53_apply(priv); + + /* enable switching */ + b53_set_forwarding(priv, 1); + + return 0; +} + + +static int b53_global_reset_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *priv = sw_to_b53(dev); + u8 gc; + + b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); + + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); + mdelay(1); + b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); + mdelay(1); + + return 0; +} + +static int b53_port_get_mib(struct switch_dev *sw_dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int port = val->port_vlan; + int len = 0; + + if (!(BIT(port) & dev->enabled_ports)) + return -1; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + for (; mibs->size > 0; mibs++) { + u64 val; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, + &val32); + val = val32; + } + + len += snprintf(dev->buf + len, B53_BUF_SIZE - len, + "%-20s: %llu\n", mibs->name, val); + } + + val->len = len; + val->value.s = dev->buf; + + return 0; +} + +static int b53_port_get_stats(struct switch_dev *sw_dev, int port, + struct switch_port_stats *stats) +{ + struct b53_device *dev = sw_to_b53(sw_dev); + const struct b53_mib_desc *mibs; + int txb_id, rxb_id; + u64 rxb, txb; + + if (!(BIT(port) & dev->enabled_ports)) + return -EINVAL; + + txb_id = B53XX_MIB_TXB_ID; + rxb_id = B53XX_MIB_RXB_ID; + + if (is5365(dev)) { + if (port == 5) + port = 8; + + mibs = b53_mibs_65; + } else if (is63xx(dev)) { + mibs = b53_mibs_63xx; + txb_id = B63XX_MIB_TXB_ID; + rxb_id = B63XX_MIB_RXB_ID; + } else { + mibs = b53_mibs; + } + + dev->buf[0] = 0; + + if (mibs->size == 8) { + b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb); + b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb); + } else { + u32 val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32); + txb = val32; + + b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32); + rxb = val32; + } + + stats->tx_bytes = txb; + stats->rx_bytes = rxb; + + return 0; +} + +static struct switch_attr b53_global_ops_25[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, +}; + +static struct switch_attr b53_global_ops_65[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, +}; + +static struct switch_attr b53_global_ops[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = b53_global_set_vlan_enable, + .get = b53_global_get_vlan_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_STRING, + .name = "ports", + .description = "Available Ports (as bitmask)", + .get = b53_global_get_ports, + }, + { + .type = SWITCH_TYPE_INT, + .name = "reset_mib", + .description = "Reset MIB counters", + .set = b53_global_reset_mib, + }, + { + .type = SWITCH_TYPE_INT, + .name = "enable_jumbo", + .description = "Enable Jumbo Frames", + .set = b53_global_set_jumbo_enable, + .get = b53_global_get_jumbo_enable, + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "allow_vid_4095", + .description = "Allow VID 4095", + .set = b53_global_set_4095_enable, + .get = b53_global_get_4095_enable, + .max = 1, + }, +}; + +static struct switch_attr b53_port_ops[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get port's MIB counters", + .get = b53_port_get_mib, + }, +}; + +static struct switch_attr b53_no_ops[] = { +}; + +static const struct switch_dev_ops b53_switch_ops_25 = { + .attr_global = { + .attr = b53_global_ops_25, + .n_attr = ARRAY_SIZE(b53_global_ops_25), + }, + .attr_port = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops_65 = { + .attr_global = { + .attr = b53_global_ops_65, + .n_attr = ARRAY_SIZE(b53_global_ops_65), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +static const struct switch_dev_ops b53_switch_ops = { + .attr_global = { + .attr = b53_global_ops, + .n_attr = ARRAY_SIZE(b53_global_ops), + }, + .attr_port = { + .attr = b53_port_ops, + .n_attr = ARRAY_SIZE(b53_port_ops), + }, + .attr_vlan = { + .attr = b53_no_ops, + .n_attr = ARRAY_SIZE(b53_no_ops), + }, + + .get_vlan_ports = b53_vlan_get_ports, + .set_vlan_ports = b53_vlan_set_ports, + .get_port_pvid = b53_port_get_pvid, + .set_port_pvid = b53_port_set_pvid, + .apply_config = b53_global_apply_config, + .reset_switch = b53_global_reset_switch, + .get_port_link = b53_port_get_link, + .set_port_link = b53_port_set_link, + .get_port_stats = b53_port_get_stats, + .phy_read16 = b53_phy_read16, + .phy_write16 = b53_phy_write16, +}; + +struct b53_chip_data { + u32 chip_id; + const char *dev_name; + const char *alias; + u16 vlans; + u16 enabled_ports; + u8 cpu_port; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + const struct switch_dev_ops *sw_ops; +}; + +#define B53_VTA_REGS \ + { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } +#define B53_VTA_REGS_9798 \ + { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } +#define B53_VTA_REGS_63XX \ + { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } + +static const struct b53_chip_data b53_switch_chips[] = { + { + .chip_id = BCM5325_DEVICE_ID, + .dev_name = "BCM5325", + .alias = "bcm5325", + .vlans = 16, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_25, + }, + { + .chip_id = BCM5365_DEVICE_ID, + .dev_name = "BCM5365", + .alias = "bcm5365", + .vlans = 256, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, + .duplex_reg = B53_DUPLEX_STAT_FE, + .sw_ops = &b53_switch_ops_65, + }, + { + .chip_id = BCM5395_DEVICE_ID, + .dev_name = "BCM5395", + .alias = "bcm5395", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5397_DEVICE_ID, + .dev_name = "BCM5397", + .alias = "bcm5397", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM5398_DEVICE_ID, + .dev_name = "BCM5398", + .alias = "bcm5398", + .vlans = 4096, + .enabled_ports = 0x7f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_9798, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53115_DEVICE_ID, + .dev_name = "BCM53115", + .alias = "bcm53115", + .vlans = 4096, + .enabled_ports = 0x1f, + .vta_regs = B53_VTA_REGS, + .cpu_port = B53_CPU_PORT, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53125_DEVICE_ID, + .dev_name = "BCM53125", + .alias = "bcm53125", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53128_DEVICE_ID, + .dev_name = "BCM53128", + .alias = "bcm53128", + .vlans = 4096, + .enabled_ports = 0x1ff, + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM63XX_DEVICE_ID, + .dev_name = "BCM63xx", + .alias = "bcm63xx", + .vlans = 4096, + .enabled_ports = 0, /* pdata must provide them */ + .cpu_port = B53_CPU_PORT, + .vta_regs = B53_VTA_REGS_63XX, + .duplex_reg = B53_DUPLEX_STAT_63XX, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53010_DEVICE_ID, + .dev_name = "BCM53010", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53011_DEVICE_ID, + .dev_name = "BCM53011", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53012_DEVICE_ID, + .dev_name = "BCM53012", + .alias = "bcm53011", + .vlans = 4096, + .enabled_ports = 0x1bf, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53018_DEVICE_ID, + .dev_name = "BCM53018", + .alias = "bcm53018", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, + { + .chip_id = BCM53019_DEVICE_ID, + .dev_name = "BCM53019", + .alias = "bcm53019", + .vlans = 4096, + .enabled_ports = 0x1f, + .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ + .vta_regs = B53_VTA_REGS, + .duplex_reg = B53_DUPLEX_STAT_GE, + .jumbo_pm_reg = B53_JUMBO_PORT_MASK, + .jumbo_size_reg = B53_JUMBO_MAX_SIZE, + .sw_ops = &b53_switch_ops, + }, +}; + +static int b53_switch_init_of(struct b53_device *dev) +{ + struct device_node *dn, *pn; + const char *alias; + u32 port_num; + u16 ports = 0; + + dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); + if (!dn) + return -EINVAL; + + for_each_available_child_of_node(dn, pn) { + const char *label; + int len; + + if (of_property_read_u32(pn, "reg", &port_num)) + continue; + + if (port_num > B53_CPU_PORT) + continue; + + ports |= BIT(port_num); + + label = of_get_property(pn, "label", &len); + if (label && !strcmp(label, "cpu")) + dev->sw_dev.cpu_port = port_num; + } + + dev->enabled_ports = ports; + + if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias", + &alias)) + dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL); + + return 0; +} + +static int b53_switch_init(struct b53_device *dev) +{ + struct switch_dev *sw_dev = &dev->sw_dev; + unsigned i; + int ret; + + for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { + const struct b53_chip_data *chip = &b53_switch_chips[i]; + + if (chip->chip_id == dev->chip_id) { + sw_dev->name = chip->dev_name; + if (!sw_dev->alias) + sw_dev->alias = chip->alias; + if (!dev->enabled_ports) + dev->enabled_ports = chip->enabled_ports; + dev->duplex_reg = chip->duplex_reg; + dev->vta_regs[0] = chip->vta_regs[0]; + dev->vta_regs[1] = chip->vta_regs[1]; + dev->vta_regs[2] = chip->vta_regs[2]; + dev->jumbo_pm_reg = chip->jumbo_pm_reg; + sw_dev->ops = chip->sw_ops; + sw_dev->cpu_port = chip->cpu_port; + sw_dev->vlans = chip->vlans; + break; + } + } + + if (!sw_dev->name) + return -EINVAL; + + /* check which BCM5325x version we have */ + if (is5325(dev)) { + u8 vc4; + + b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); + + /* check reserved bits */ + switch (vc4 & 3) { + case 1: + /* BCM5325E */ + break; + case 3: + /* BCM5325F - do not use port 4 */ + dev->enabled_ports &= ~BIT(4); + break; + default: +/* On the BCM47XX SoCs this is the supported internal switch.*/ +#ifndef CONFIG_BCM47XX + /* BCM5325M */ + return -EINVAL; +#else + break; +#endif + } + } else if (dev->chip_id == BCM53115_DEVICE_ID) { + u64 strap_value; + + b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); + /* use second IMP port if GMII is enabled */ + if (strap_value & SV_GMII_CTRL_115) + sw_dev->cpu_port = 5; + } + + if (dev_of_node(dev->dev)) { + ret = b53_switch_init_of(dev); + if (ret) + return ret; + } + + dev->enabled_ports |= BIT(sw_dev->cpu_port); + sw_dev->ports = fls(dev->enabled_ports); + + dev->ports = devm_kzalloc(dev->dev, + sizeof(struct b53_port) * sw_dev->ports, + GFP_KERNEL); + if (!dev->ports) + return -ENOMEM; + + dev->vlans = devm_kzalloc(dev->dev, + sizeof(struct b53_vlan) * sw_dev->vlans, + GFP_KERNEL); + if (!dev->vlans) + return -ENOMEM; + + dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); + if (!dev->buf) + return -ENOMEM; + + dev->reset_gpio = b53_switch_get_reset_gpio(dev); + if (dev->reset_gpio >= 0) { + ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, + GPIOF_OUT_INIT_HIGH, "robo_reset"); + if (ret) + return ret; + } + + return b53_switch_reset(dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv) +{ + struct b53_device *dev; + + dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + + dev->dev = base; + dev->ops = ops; + dev->priv = priv; + mutex_init(&dev->reg_mutex); + + return dev; +} +EXPORT_SYMBOL(b53_switch_alloc); + +int b53_switch_detect(struct b53_device *dev) +{ + u32 id32; + u16 tmp; + u8 id8; + int ret; + + ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); + if (ret) + return ret; + + switch (id8) { + case 0: + /* + * BCM5325 and BCM5365 do not have this register so reads + * return 0. But the read operation did succeed, so assume + * this is one of them. + * + * Next check if we can write to the 5325's VTA register; for + * 5365 it is read only. + */ + + b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); + b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); + + if (tmp == 0xf) + dev->chip_id = BCM5325_DEVICE_ID; + else + dev->chip_id = BCM5365_DEVICE_ID; + break; + case BCM5395_DEVICE_ID: + case BCM5397_DEVICE_ID: + case BCM5398_DEVICE_ID: + dev->chip_id = id8; + break; + default: + ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); + if (ret) + return ret; + + switch (id32) { + case BCM53115_DEVICE_ID: + case BCM53125_DEVICE_ID: + case BCM53128_DEVICE_ID: + case BCM53010_DEVICE_ID: + case BCM53011_DEVICE_ID: + case BCM53012_DEVICE_ID: + case BCM53018_DEVICE_ID: + case BCM53019_DEVICE_ID: + dev->chip_id = id32; + break; + default: + pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", + id8, id32); + return -ENODEV; + } + } + + if (dev->chip_id == BCM5325_DEVICE_ID) + return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, + &dev->core_rev); + else + return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, + &dev->core_rev); +} +EXPORT_SYMBOL(b53_switch_detect); + +int b53_switch_register(struct b53_device *dev) +{ + int ret; + + if (dev->pdata) { + dev->chip_id = dev->pdata->chip_id; + dev->enabled_ports = dev->pdata->enabled_ports; + dev->sw_dev.alias = dev->pdata->alias; + } + + if (!dev->chip_id && b53_switch_detect(dev)) + return -EINVAL; + + ret = b53_switch_init(dev); + if (ret) + return ret; + + pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); + + return register_switch(&dev->sw_dev, NULL); +} +EXPORT_SYMBOL(b53_switch_register); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 switch library"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mdio.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mdio.c new file mode 100644 index 0000000000..75bb4d941c --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mdio.c @@ -0,0 +1,436 @@ +/* + * B53 register access through MII registers + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "b53_priv.h" + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +/* MII registers */ +#define REG_MII_PAGE 0x10 /* MII Page register */ +#define REG_MII_ADDR 0x11 /* MII Address register */ +#define REG_MII_DATA0 0x18 /* MII Data register 0 */ +#define REG_MII_DATA1 0x19 /* MII Data register 1 */ +#define REG_MII_DATA2 0x1a /* MII Data register 2 */ +#define REG_MII_DATA3 0x1b /* MII Data register 3 */ + +#define REG_MII_PAGE_ENABLE BIT(0) +#define REG_MII_ADDR_WRITE BIT(0) +#define REG_MII_ADDR_READ BIT(1) + +static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) +{ + int i; + u16 v; + int ret; + struct mii_bus *bus = dev->priv; + + if (dev->current_page != page) { + /* set page number */ + v = (page << 8) | REG_MII_PAGE_ENABLE; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); + if (ret) + return ret; + dev->current_page = page; + } + + /* set register address */ + v = (reg << 8) | op; + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); + if (ret) + return ret; + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); + if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; + + return 0; +} + +static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + + return 0; +} + +static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); + *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; + + return 0; +} + +static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 2; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + struct mii_bus *bus = dev->priv; + u64 temp = 0; + int i; + int ret; + + ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); + if (ret) + return ret; + + for (i = 3; i >= 0; i--) { + temp <<= 16; + temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); + } + + *val = temp; + + return 0; +} + +static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + int ret; + + ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); + if (ret) + return ret; + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + struct mii_bus *bus = dev->priv; + unsigned int i; + u32 temp = value; + + for (i = 0; i < 2; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 3; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); + +} + +static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + struct mii_bus *bus = dev->priv; + unsigned i; + u64 temp = value; + + for (i = 0; i < 4; i++) { + int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, + temp & 0xffff); + if (ret) + return ret; + temp >>= 16; + } + + return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); +} + +static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg, + u16 *value) +{ + struct mii_bus *bus = dev->priv; + + *value = mdiobus_read(bus, addr, reg); + + return 0; +} + +static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg, + u16 value) +{ + struct mii_bus *bus = dev->priv; + + return mdiobus_write(bus, addr, reg, value); +} + +static struct b53_io_ops b53_mdio_ops = { + .read8 = b53_mdio_read8, + .read16 = b53_mdio_read16, + .read32 = b53_mdio_read32, + .read48 = b53_mdio_read48, + .read64 = b53_mdio_read64, + .write8 = b53_mdio_write8, + .write16 = b53_mdio_write16, + .write32 = b53_mdio_write32, + .write48 = b53_mdio_write48, + .write64 = b53_mdio_write64, + .phy_read16 = b53_mdio_phy_read16, + .phy_write16 = b53_mdio_phy_write16, +}; + +static int b53_phy_probe(struct phy_device *phydev) +{ + struct b53_device dev; + int ret; + + /* allow the generic phy driver to take over */ + if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0) + return -ENODEV; + + dev.current_page = 0xff; + dev.priv = phydev->mdio.bus; + dev.ops = &b53_mdio_ops; + dev.pdata = NULL; + mutex_init(&dev.reg_mutex); + + ret = b53_switch_detect(&dev); + if (ret) + return ret; + + if (is5325(&dev) || is5365(&dev)) + phydev->supported = SUPPORTED_100baseT_Full; + else + phydev->supported = SUPPORTED_1000baseT_Full; + + phydev->advertising = phydev->supported; + + return 0; +} + +static int b53_phy_config_init(struct phy_device *phydev) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus); + if (!dev) + return -ENOMEM; + + /* we don't use page 0xff, so force a page set */ + dev->current_page = 0xff; + /* force the ethX as alias */ + dev->sw_dev.alias = phydev->attached_dev->name; + + ret = b53_switch_register(dev); + if (ret) { + dev_err(dev->dev, "failed to register switch: %i\n", ret); + return ret; + } + + phydev->priv = dev; + + return 0; +} + +static void b53_phy_remove(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (!priv) + return; + + b53_switch_remove(priv); + + phydev->priv = NULL; +} + +static int b53_phy_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int b53_phy_read_status(struct phy_device *phydev) +{ + struct b53_device *priv = phydev->priv; + + if (is5325(priv) || is5365(priv)) + phydev->speed = 100; + else + phydev->speed = 1000; + + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + phydev->state = PHY_RUNNING; + + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +/* BCM5325, BCM539x */ +static struct phy_driver b53_phy_driver_id1 = { + .phy_id = 0x0143bc00, + .name = "Broadcom B53 (1)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM53125, BCM53128 */ +static struct phy_driver b53_phy_driver_id2 = { + .phy_id = 0x03625c00, + .name = "Broadcom B53 (2)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +/* BCM5365 */ +static struct phy_driver b53_phy_driver_id3 = { + .phy_id = 0x00406000, + .name = "Broadcom B53 (3)", + .phy_id_mask = 0x1ffffc00, + .features = 0, + .probe = b53_phy_probe, + .remove = b53_phy_remove, + .config_aneg = b53_phy_config_aneg, + .config_init = b53_phy_config_init, + .read_status = b53_phy_read_status, +}; + +int __init b53_phy_driver_register(void) +{ + int ret; + + ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE); + if (ret) + return ret; + + ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE); + if (ret) + goto err1; + + ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE); + if (!ret) + return 0; + + phy_driver_unregister(&b53_phy_driver_id2); +err1: + phy_driver_unregister(&b53_phy_driver_id1); + return ret; +} + +void __exit b53_phy_driver_unregister(void) +{ + phy_driver_unregister(&b53_phy_driver_id3); + phy_driver_unregister(&b53_phy_driver_id2); + phy_driver_unregister(&b53_phy_driver_id1); +} + +module_init(b53_phy_driver_register); +module_exit(b53_phy_driver_unregister); + +MODULE_DESCRIPTION("B53 MDIO access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mmap.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mmap.c new file mode 100644 index 0000000000..ab1895e9b5 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_mmap.c @@ -0,0 +1,241 @@ +/* + * B53 register access through memory mapped registers + * + * Copyright (C) 2012-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + + *val = readb(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readw_be(regs + (page << 8) + reg); + else + *val = readw(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + *val = readl_be(regs + (page << 8) + reg); + else + *val = readl(regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u16 lo; + u32 hi; + + b53_mmap_read16(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 2, &hi); + + *val = ((u64)hi << 16) | lo; + } else { + u32 lo; + u16 hi; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read16(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + } + + return 0; +} + +static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u32 hi, lo; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_read32(dev, page, reg, &lo); + b53_mmap_read32(dev, page, reg + 4, &hi); + + *val = ((u64)hi << 32) | lo; + + return 0; +} + +static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + + writeb(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writew_be(value, regs + (page << 8) + reg); + else + writew(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + if (dev->pdata && dev->pdata->big_endian) + writel_be(value, regs + (page << 8) + reg); + else + writel(value, regs + (page << 8) + reg); + + return 0; +} + +static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + if (WARN_ON(reg % 2)) + return -EINVAL; + + if (reg % 4) { + u32 hi = (u32)(value >> 16); + u16 lo = (u16)value; + + b53_mmap_write16(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 2, hi); + } else { + u16 hi = (u16)(value >> 32); + u32 lo = (u32)value; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write16(dev, page, reg + 4, hi); + } + + return 0; +} + +static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u32 hi, lo; + + hi = (u32)(value >> 32); + lo = (u32)value; + + if (WARN_ON(reg % 4)) + return -EINVAL; + + b53_mmap_write32(dev, page, reg, lo); + b53_mmap_write32(dev, page, reg + 4, hi); + + return 0; +} + +static struct b53_io_ops b53_mmap_ops = { + .read8 = b53_mmap_read8, + .read16 = b53_mmap_read16, + .read32 = b53_mmap_read32, + .read48 = b53_mmap_read48, + .read64 = b53_mmap_read64, + .write8 = b53_mmap_write8, + .write16 = b53_mmap_write16, + .write32 = b53_mmap_write32, + .write48 = b53_mmap_write48, + .write64 = b53_mmap_write64, +}; + +static int b53_mmap_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_mmap_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_mmap_driver = { + .probe = b53_mmap_probe, + .remove = b53_mmap_remove, + .driver = { + .name = "b53-switch", + }, +}; + +module_platform_driver(b53_mmap_driver); +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 MMAP access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_phy_fixup.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_phy_fixup.c new file mode 100644 index 0000000000..e2f8a39ab6 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_phy_fixup.c @@ -0,0 +1,55 @@ +/* + * B53 PHY Fixup call + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ + +#define B53_BRCM_OUI_1 0x0143bc00 +#define B53_BRCM_OUI_2 0x03625c00 +#define B53_BRCM_OUI_3 0x00406000 + +static int b53_phy_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u32 phy_id; + + if (dev->mdio.addr != B53_PSEUDO_PHY) + return 0; + + /* read the first port's id */ + phy_id = mdiobus_read(bus, 0, 2) << 16; + phy_id |= mdiobus_read(bus, 0, 3); + + if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 || + (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) { + dev->phy_id = phy_id; + } + + return 0; +} + +int __init b53_phy_fixup_register(void) +{ + return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup); +} + +subsys_initcall(b53_phy_fixup_register); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_priv.h b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_priv.h new file mode 100644 index 0000000000..a9296c9421 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_priv.h @@ -0,0 +1,341 @@ +/* + * B53 common definitions + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_PRIV_H +#define __B53_PRIV_H + +#include +#include +#include + +struct b53_device; + +struct b53_io_ops { + int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); + int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); + int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); + int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); + int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); + int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); + int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); + int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); + int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value); +}; + +enum { + BCM5325_DEVICE_ID = 0x25, + BCM5365_DEVICE_ID = 0x65, + BCM5395_DEVICE_ID = 0x95, + BCM5397_DEVICE_ID = 0x97, + BCM5398_DEVICE_ID = 0x98, + BCM53115_DEVICE_ID = 0x53115, + BCM53125_DEVICE_ID = 0x53125, + BCM53128_DEVICE_ID = 0x53128, + BCM63XX_DEVICE_ID = 0x6300, + BCM53010_DEVICE_ID = 0x53010, + BCM53011_DEVICE_ID = 0x53011, + BCM53012_DEVICE_ID = 0x53012, + BCM53018_DEVICE_ID = 0x53018, + BCM53019_DEVICE_ID = 0x53019, +}; + +#define B53_N_PORTS 9 +#define B53_N_PORTS_25 6 + +struct b53_vlan { + unsigned int members:B53_N_PORTS; + unsigned int untag:B53_N_PORTS; +}; + +struct b53_port { + unsigned int pvid:12; +}; + +struct b53_device { + struct switch_dev sw_dev; + struct b53_platform_data *pdata; + + struct mutex reg_mutex; + const struct b53_io_ops *ops; + + /* chip specific data */ + u32 chip_id; + u8 core_rev; + u8 vta_regs[3]; + u8 duplex_reg; + u8 jumbo_pm_reg; + u8 jumbo_size_reg; + int reset_gpio; + + /* used ports mask */ + u16 enabled_ports; + + /* connect specific data */ + u8 current_page; + struct device *dev; + void *priv; + + /* run time configuration */ + unsigned enable_vlan:1; + unsigned enable_jumbo:1; + unsigned allow_vid_4095:1; + + struct b53_port *ports; + struct b53_vlan *vlans; + + char *buf; +}; + +#define b53_for_each_port(dev, i) \ + for (i = 0; i < B53_N_PORTS; i++) \ + if (dev->enabled_ports & BIT(i)) + + + +static inline int is5325(struct b53_device *dev) +{ + return dev->chip_id == BCM5325_DEVICE_ID; +} + +static inline int is5365(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + return dev->chip_id == BCM5365_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5397_98(struct b53_device *dev) +{ + return dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is539x(struct b53_device *dev) +{ + return dev->chip_id == BCM5395_DEVICE_ID || + dev->chip_id == BCM5397_DEVICE_ID || + dev->chip_id == BCM5398_DEVICE_ID; +} + +static inline int is531x5(struct b53_device *dev) +{ + return dev->chip_id == BCM53115_DEVICE_ID || + dev->chip_id == BCM53125_DEVICE_ID || + dev->chip_id == BCM53128_DEVICE_ID; +} + +static inline int is63xx(struct b53_device *dev) +{ +#ifdef CONFIG_BCM63XX + return dev->chip_id == BCM63XX_DEVICE_ID; +#else + return 0; +#endif +} + +static inline int is5301x(struct b53_device *dev) +{ + return dev->chip_id == BCM53010_DEVICE_ID || + dev->chip_id == BCM53011_DEVICE_ID || + dev->chip_id == BCM53012_DEVICE_ID || + dev->chip_id == BCM53018_DEVICE_ID || + dev->chip_id == BCM53019_DEVICE_ID; +} + +#define B53_CPU_PORT_25 5 +#define B53_CPU_PORT 8 + +static inline int is_cpu_port(struct b53_device *dev, int port) +{ + return dev->sw_dev.cpu_port == port; +} + +static inline int is_imp_port(struct b53_device *dev, int port) +{ + if (is5325(dev) || is5365(dev)) + return port == B53_CPU_PORT_25; + else + return port == B53_CPU_PORT; +} + +static inline struct b53_device *sw_to_b53(struct switch_dev *sw) +{ + return container_of(sw, struct b53_device, sw_dev); +} + +struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, + void *priv); + +int b53_switch_detect(struct b53_device *dev); + +int b53_switch_register(struct b53_device *dev); + +static inline void b53_switch_remove(struct b53_device *dev) +{ + unregister_switch(&dev->sw_dev); +} + +static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read8(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read16(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read32(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read48(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->read64(dev, page, reg, val); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write8(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write16(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write32(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write48(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + int ret; + + mutex_lock(&dev->reg_mutex); + ret = dev->ops->write64(dev, page, reg, value); + mutex_unlock(&dev->reg_mutex); + + return ret; +} + +#ifdef CONFIG_BCM47XX +#include +#endif + +#include +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) +#include +#endif +static inline int b53_switch_get_reset_gpio(struct b53_device *dev) +{ +#ifdef CONFIG_BCM47XX + enum bcm47xx_board board = bcm47xx_board_get(); + + switch (board) { + case BCM47XX_BOARD_LINKSYS_WRT300NV11: + case BCM47XX_BOARD_LINKSYS_WRT310NV1: + return 8; + default: + break; + } +#endif + +#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) + return bcm47xx_nvram_gpio_pin("robo_reset"); +#else + return -ENOENT; +#endif +} + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_regs.h b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_regs.h new file mode 100644 index 0000000000..f0bf6744a7 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_regs.h @@ -0,0 +1,348 @@ +/* + * B53 register definitions + * + * Copyright (C) 2004 Broadcom Corporation + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_REGS_H +#define __B53_REGS_H + +/* Management Port (SMP) Page offsets */ +#define B53_CTRL_PAGE 0x00 /* Control */ +#define B53_STAT_PAGE 0x01 /* Status */ +#define B53_MGMT_PAGE 0x02 /* Management Mode */ +#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ +#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ +#define B53_ARLIO_PAGE 0x05 /* ARL Access */ +#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ +#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ + +/* PHY Registers */ +#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */ +#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ +#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ + +/* MIB registers */ +#define B53_MIB_PAGE(i) (0x20 + (i)) + +/* Quality of Service (QoS) Registers */ +#define B53_QOS_PAGE 0x30 + +/* Port VLAN Page */ +#define B53_PVLAN_PAGE 0x31 + +/* VLAN Registers */ +#define B53_VLAN_PAGE 0x34 + +/* Jumbo Frame Registers */ +#define B53_JUMBO_PAGE 0x40 + +/* CFP Configuration Registers Page */ +#define B53_CFP_PAGE 0xa1 + +/************************************************************************* + * Control Page registers + *************************************************************************/ + +/* Port Control Register (8 bit) */ +#define B53_PORT_CTRL(i) (0x00 + (i)) +#define PORT_CTRL_RX_DISABLE BIT(0) +#define PORT_CTRL_TX_DISABLE BIT(1) +#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ +#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ +#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ +#define PORT_CTRL_STP_STATE_S 5 +#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S) + +/* SMP Control Register (8 bit) */ +#define B53_SMP_CTRL 0x0a + +/* Switch Mode Control Register (8 bit) */ +#define B53_SWITCH_MODE 0x0b +#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ +#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ + +/* IMP Port state override register (8 bit) */ +#define B53_PORT_OVERRIDE_CTRL 0x0e +#define PORT_OVERRIDE_LINK BIT(0) +#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define PORT_OVERRIDE_SPEED_S 2 +#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) +#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ +#define PORT_OVERRIDE_RX_FLOW BIT(4) +#define PORT_OVERRIDE_TX_FLOW BIT(5) +#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */ +#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ + +/* Power-down mode control */ +#define B53_PD_MODE_CTRL_25 0x0f + +/* IP Multicast control (8 bit) */ +#define B53_IP_MULTICAST_CTRL 0x21 +#define B53_IPMC_FWD_EN BIT(1) +#define B53_UC_FWD_EN BIT(6) +#define B53_MC_FWD_EN BIT(7) + +/* (16 bit) */ +#define B53_UC_FLOOD_MASK 0x32 +#define B53_MC_FLOOD_MASK 0x34 +#define B53_IPMC_FLOOD_MASK 0x36 + +/* + * Override Ports 0-7 State on devices with xMII interfaces (8 bit) + * + * For port 8 still use B53_PORT_OVERRIDE_CTRL + * Please note that not all ports are available on every hardware, e.g. BCM5301X + * don't include overriding port 6, BCM63xx also have some limitations. + */ +#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i)) +#define GMII_PO_LINK BIT(0) +#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ +#define GMII_PO_SPEED_S 2 +#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S) +#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S) +#define GMII_PO_RX_FLOW BIT(4) +#define GMII_PO_TX_FLOW BIT(5) +#define GMII_PO_EN BIT(6) /* Use the register contents */ +#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */ + +/* Software reset register (8 bit) */ +#define B53_SOFTRESET 0x79 + +/* Fast Aging Control register (8 bit) */ +#define B53_FAST_AGE_CTRL 0x88 +#define FAST_AGE_STATIC BIT(0) +#define FAST_AGE_DYNAMIC BIT(1) +#define FAST_AGE_PORT BIT(2) +#define FAST_AGE_VLAN BIT(3) +#define FAST_AGE_STP BIT(4) +#define FAST_AGE_MC BIT(5) +#define FAST_AGE_DONE BIT(7) + +/************************************************************************* + * Status Page registers + *************************************************************************/ + +/* Link Status Summary Register (16bit) */ +#define B53_LINK_STAT 0x00 + +/* Link Status Change Register (16 bit) */ +#define B53_LINK_STAT_CHANGE 0x02 + +/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ +#define B53_SPEED_STAT 0x04 +#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) +#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) +#define SPEED_STAT_10M 0 +#define SPEED_STAT_100M 1 +#define SPEED_STAT_1000M 2 + +/* Duplex Status Summary (16 bit) */ +#define B53_DUPLEX_STAT_FE 0x06 +#define B53_DUPLEX_STAT_GE 0x08 +#define B53_DUPLEX_STAT_63XX 0x0c + +/* Revision ID register for BCM5325 */ +#define B53_REV_ID_25 0x50 + +/* Strap Value (48 bit) */ +#define B53_STRAP_VALUE 0x70 +#define SV_GMII_CTRL_115 BIT(27) + +/************************************************************************* + * Management Mode Page Registers + *************************************************************************/ + +/* Global Management Config Register (8 bit) */ +#define B53_GLOBAL_CONFIG 0x00 +#define GC_RESET_MIB 0x01 +#define GC_RX_BPDU_EN 0x02 +#define GC_MIB_AC_HDR_EN 0x10 +#define GC_MIB_AC_EN 0x20 +#define GC_FRM_MGMT_PORT_M 0xC0 +#define GC_FRM_MGMT_PORT_04 0x00 +#define GC_FRM_MGMT_PORT_MII 0x80 + +/* Broadcom Header control register (8 bit) */ +#define B53_BRCM_HDR 0x03 +#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */ +#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */ + +/* Device ID register (8 or 32 bit) */ +#define B53_DEVICE_ID 0x30 + +/* Revision ID register (8 bit) */ +#define B53_REV_ID 0x40 + +/************************************************************************* + * ARL Access Page Registers + *************************************************************************/ + +/* VLAN Table Access Register (8 bit) */ +#define B53_VT_ACCESS 0x80 +#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ +#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ +#define VTA_CMD_WRITE 0 +#define VTA_CMD_READ 1 +#define VTA_CMD_CLEAR 2 +#define VTA_START_CMD BIT(7) + +/* VLAN Table Index Register (16 bit) */ +#define B53_VT_INDEX 0x81 +#define B53_VT_INDEX_9798 0x61 +#define B53_VT_INDEX_63XX 0x62 + +/* VLAN Table Entry Register (32 bit) */ +#define B53_VT_ENTRY 0x83 +#define B53_VT_ENTRY_9798 0x63 +#define B53_VT_ENTRY_63XX 0x64 +#define VTE_MEMBERS 0x1ff +#define VTE_UNTAG_S 9 +#define VTE_UNTAG (0x1ff << 9) + +/************************************************************************* + * Port VLAN Registers + *************************************************************************/ + +/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ +#define B53_PVLAN_PORT_MASK(i) ((i) * 2) + +/************************************************************************* + * 802.1Q Page Registers + *************************************************************************/ + +/* Global QoS Control (8 bit) */ +#define B53_QOS_GLOBAL_CTL 0x00 + +/* Enable 802.1Q for individual Ports (16 bit) */ +#define B53_802_1P_EN 0x04 + +/************************************************************************* + * VLAN Page Registers + *************************************************************************/ + +/* VLAN Control 0 (8 bit) */ +#define B53_VLAN_CTRL0 0x00 +#define VC0_8021PF_CTRL_MASK 0x3 +#define VC0_8021PF_CTRL_NONE 0x0 +#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021PF_CTRL_CHANGE_VID 0x2 +#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 +#define VC0_8021QF_CTRL_MASK 0xc +#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 +#define VC0_8021QF_CTRL_CHANGE_VID 0x2 +#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 +#define VC0_RESERVED_1 BIT(1) +#define VC0_DROP_VID_MISS BIT(4) +#define VC0_VID_HASH_VID BIT(5) +#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ +#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ + +/* VLAN Control 1 (8 bit) */ +#define B53_VLAN_CTRL1 0x01 +#define VC1_RX_MCST_TAG_EN BIT(1) +#define VC1_RX_MCST_FWD_EN BIT(2) +#define VC1_RX_MCST_UNTAG_EN BIT(3) + +/* VLAN Control 2 (8 bit) */ +#define B53_VLAN_CTRL2 0x02 + +/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ +#define B53_VLAN_CTRL3 0x03 +#define B53_VLAN_CTRL3_63XX 0x04 +#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ +#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ + +/* VLAN Control 4 (8 bit) */ +#define B53_VLAN_CTRL4 0x05 +#define B53_VLAN_CTRL4_25 0x04 +#define B53_VLAN_CTRL4_63XX 0x06 +#define VC4_ING_VID_CHECK_S 6 +#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) +#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ +#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ +#define VC4_NO_ING_VID_CHK 2 /* do not check */ +#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ + +/* VLAN Control 5 (8 bit) */ +#define B53_VLAN_CTRL5 0x06 +#define B53_VLAN_CTRL5_25 0x05 +#define B53_VLAN_CTRL5_63XX 0x07 +#define VC5_VID_FFF_EN BIT(2) +#define VC5_DROP_VTABLE_MISS BIT(3) + +/* VLAN Control 6 (8 bit) */ +#define B53_VLAN_CTRL6 0x07 +#define B53_VLAN_CTRL6_63XX 0x08 + +/* VLAN Table Access Register (16 bit) */ +#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ +#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ +#define VTA_VID_LOW_MASK_25 0xf +#define VTA_VID_LOW_MASK_65 0xff +#define VTA_VID_HIGH_S_25 4 +#define VTA_VID_HIGH_S_65 8 +#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) +#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) +#define VTA_RW_STATE BIT(12) +#define VTA_RW_STATE_RD 0 +#define VTA_RW_STATE_WR BIT(12) +#define VTA_RW_OP_EN BIT(13) + +/* VLAN Read/Write Registers for (16/32 bit) */ +#define B53_VLAN_WRITE_25 0x08 +#define B53_VLAN_WRITE_65 0x0a +#define B53_VLAN_READ 0x0c +#define VA_MEMBER_MASK 0x3f +#define VA_UNTAG_S_25 6 +#define VA_UNTAG_MASK_25 0x3f +#define VA_UNTAG_S_65 7 +#define VA_UNTAG_MASK_65 0x1f +#define VA_VID_HIGH_S 12 +#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) +#define VA_VALID_25 BIT(20) +#define VA_VALID_25_R4 BIT(24) +#define VA_VALID_65 BIT(14) + +/* VLAN Port Default Tag (16 bit) */ +#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) + +/************************************************************************* + * Jumbo Frame Page Registers + *************************************************************************/ + +/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ +#define B53_JUMBO_PORT_MASK 0x01 +#define B53_JUMBO_PORT_MASK_63XX 0x04 +#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ + +/* Good Frame Max Size without 802.1Q TAG (16 bit) */ +#define B53_JUMBO_MAX_SIZE 0x05 +#define B53_JUMBO_MAX_SIZE_63XX 0x08 +#define JMS_MIN_SIZE 1518 +#define JMS_MAX_SIZE 9724 + +/************************************************************************* + * CFP Configuration Page Registers + *************************************************************************/ + +/* CFP Control Register with ports map (8 bit) */ +#define B53_CFP_CTRL 0x00 + +#endif /* !__B53_REGS_H */ diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_spi.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_spi.c new file mode 100644 index 0000000000..efc8f7ee89 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_spi.c @@ -0,0 +1,344 @@ +/* + * B53 register access through SPI + * + * Copyright (C) 2011-2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "b53_priv.h" + +#define B53_SPI_DATA 0xf0 + +#define B53_SPI_STATUS 0xfe +#define B53_SPI_CMD_SPIF BIT(7) +#define B53_SPI_CMD_RACK BIT(5) + +#define B53_SPI_CMD_READ 0x00 +#define B53_SPI_CMD_WRITE 0x01 +#define B53_SPI_CMD_NORMAL 0x60 +#define B53_SPI_CMD_FAST 0x10 + +#define B53_SPI_PAGE_SELECT 0xff + +static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, + unsigned len) +{ + u8 txbuf[2]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; + txbuf[1] = reg; + + return spi_write_then_read(spi, txbuf, 2, val, len); +} + +static inline int b53_spi_clear_status(struct spi_device *spi) +{ + unsigned int i; + u8 rxbuf; + int ret; + + for (i = 0; i < 10; i++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (!(rxbuf & B53_SPI_CMD_SPIF)) + break; + + mdelay(1); + } + + if (i == 10) + return -EIO; + + return 0; +} + +static inline int b53_spi_set_page(struct spi_device *spi, u8 page) +{ + u8 txbuf[3]; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = B53_SPI_PAGE_SELECT; + txbuf[2] = page; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) +{ + int ret = b53_spi_clear_status(spi); + + if (ret) + return ret; + + return b53_spi_set_page(spi, page); +} + +static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) +{ + u8 rxbuf; + int retry_count; + int ret; + + ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); + if (ret) + return ret; + + for (retry_count = 0; retry_count < 10; retry_count++) { + ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); + if (ret) + return ret; + + if (rxbuf & B53_SPI_CMD_RACK) + break; + + mdelay(1); + } + + if (retry_count == 10) + return -EIO; + + return 0; +} + +static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, + unsigned len) +{ + struct spi_device *spi = dev->priv; + int ret; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + ret = b53_spi_prepare_reg_read(spi, reg); + if (ret) + return ret; + + return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); +} + +static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + return b53_spi_read(dev, page, reg, val, 1); +} + +static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); + + if (!ret) + *val = le16_to_cpu(*val); + + return ret; +} + +static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); + + if (!ret) + *val = le32_to_cpu(*val); + + return ret; +} + +static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret; + + *val = 0; + ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); + + if (!ret) + *val = le64_to_cpu(*val); + + return ret; +} + +static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[3]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + txbuf[2] = value; + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[4]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le16(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[6]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le32(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf) - 2); +} + +static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) +{ + struct spi_device *spi = dev->priv; + int ret; + u8 txbuf[10]; + + ret = b53_prepare_reg_access(spi, page); + if (ret) + return ret; + + txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; + txbuf[1] = reg; + put_unaligned_le64(value, &txbuf[2]); + + return spi_write(spi, txbuf, sizeof(txbuf)); +} + +static struct b53_io_ops b53_spi_ops = { + .read8 = b53_spi_read8, + .read16 = b53_spi_read16, + .read32 = b53_spi_read32, + .read48 = b53_spi_read48, + .read64 = b53_spi_read64, + .write8 = b53_spi_write8, + .write16 = b53_spi_write16, + .write32 = b53_spi_write32, + .write48 = b53_spi_write48, + .write64 = b53_spi_write64, +}; + +static int b53_spi_probe(struct spi_device *spi) +{ + struct b53_device *dev; + int ret; + + dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); + if (!dev) + return -ENOMEM; + + if (spi->dev.platform_data) + dev->pdata = spi->dev.platform_data; + + ret = b53_switch_register(dev); + if (ret) + return ret; + + spi_set_drvdata(spi, dev); + + return 0; +} + +static int b53_spi_remove(struct spi_device *spi) +{ + struct b53_device *dev = spi_get_drvdata(spi); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static const struct of_device_id b53_of_match[] = { + { .compatible = "brcm,bcm5325" }, + { .compatible = "brcm,bcm53115" }, + { .compatible = "brcm,bcm53125" }, + { .compatible = "brcm,bcm53128" }, + { .compatible = "brcm,bcm5365" }, + { .compatible = "brcm,bcm5395" }, + { .compatible = "brcm,bcm5397" }, + { .compatible = "brcm,bcm5398" }, + { /* sentinel */ }, +}; + +static struct spi_driver b53_spi_driver = { + .driver = { + .name = "b53-switch", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + .of_match_table = b53_of_match, + }, + .probe = b53_spi_probe, + .remove = b53_spi_remove, +}; + +module_spi_driver(b53_spi_driver); + +MODULE_AUTHOR("Jonas Gorski "); +MODULE_DESCRIPTION("B53 SPI access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_srab.c b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_srab.c new file mode 100644 index 0000000000..012daa3a51 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/b53/b53_srab.c @@ -0,0 +1,378 @@ +/* + * B53 register access through Switch Register Access Bridge Registers + * + * Copyright (C) 2013 Hauke Mehrtens + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "b53_priv.h" + +/* command and status register of the SRAB */ +#define B53_SRAB_CMDSTAT 0x2c +#define B53_SRAB_CMDSTAT_RST BIT(2) +#define B53_SRAB_CMDSTAT_WRITE BIT(1) +#define B53_SRAB_CMDSTAT_GORDYN BIT(0) +#define B53_SRAB_CMDSTAT_PAGE 24 +#define B53_SRAB_CMDSTAT_REG 16 + +/* high order word of write data to switch registe */ +#define B53_SRAB_WD_H 0x30 + +/* low order word of write data to switch registe */ +#define B53_SRAB_WD_L 0x34 + +/* high order word of read data from switch register */ +#define B53_SRAB_RD_H 0x38 + +/* low order word of read data from switch register */ +#define B53_SRAB_RD_L 0x3c + +/* command and status register of the SRAB */ +#define B53_SRAB_CTRLS 0x40 +#define B53_SRAB_CTRLS_RCAREQ BIT(3) +#define B53_SRAB_CTRLS_RCAGNT BIT(4) +#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) + +/* the register captures interrupt pulses from the switch */ +#define B53_SRAB_INTR 0x44 + +static int b53_srab_request_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + int i; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls |= B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); + + for (i = 0; i < 20; i++) { + ctrls = readl(regs + B53_SRAB_CTRLS); + if (ctrls & B53_SRAB_CTRLS_RCAGNT) + break; + usleep_range(10, 100); + } + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static void b53_srab_release_grant(struct b53_device *dev) +{ + u8 __iomem *regs = dev->priv; + u32 ctrls; + + ctrls = readl(regs + B53_SRAB_CTRLS); + ctrls &= ~B53_SRAB_CTRLS_RCAREQ; + writel(ctrls, regs + B53_SRAB_CTRLS); +} + +static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) +{ + int i; + u32 cmdstat; + u8 __iomem *regs = dev->priv; + + /* set register address */ + cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | + (reg << B53_SRAB_CMDSTAT_REG) | + B53_SRAB_CMDSTAT_GORDYN | + op; + writel(cmdstat, regs + B53_SRAB_CMDSTAT); + + /* check if operation completed */ + for (i = 0; i < 5; ++i) { + cmdstat = readl(regs + B53_SRAB_CMDSTAT); + if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) + break; + usleep_range(10, 100); + } + + if (WARN_ON(i == 5)) + return -EIO; + + return 0; +} + +static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L) & 0xffff; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + ret = b53_srab_op(dev, page, reg, 0); + if (ret) + goto err; + + *val = readl(regs + B53_SRAB_RD_L); + *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, + u16 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, + u32 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel(value, regs + B53_SRAB_WD_L); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u16)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; + +} + +static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, + u64 value) +{ + u8 __iomem *regs = dev->priv; + int ret = 0; + + ret = b53_srab_request_grant(dev); + if (ret) + goto err; + + writel((u32)value, regs + B53_SRAB_WD_L); + writel((u32)(value >> 32), regs + B53_SRAB_WD_H); + + ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); + +err: + b53_srab_release_grant(dev); + + return ret; +} + +static struct b53_io_ops b53_srab_ops = { + .read8 = b53_srab_read8, + .read16 = b53_srab_read16, + .read32 = b53_srab_read32, + .read48 = b53_srab_read48, + .read64 = b53_srab_read64, + .write8 = b53_srab_write8, + .write16 = b53_srab_write16, + .write32 = b53_srab_write32, + .write48 = b53_srab_write48, + .write64 = b53_srab_write64, +}; + +static int b53_srab_probe(struct platform_device *pdev) +{ + struct b53_platform_data *pdata = pdev->dev.platform_data; + struct b53_device *dev; + + if (!pdata) + return -EINVAL; + + dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs); + if (!dev) + return -ENOMEM; + + if (pdata) + dev->pdata = pdata; + + platform_set_drvdata(pdev, dev); + + return b53_switch_register(dev); +} + +static int b53_srab_remove(struct platform_device *pdev) +{ + struct b53_device *dev = platform_get_drvdata(pdev); + + if (dev) + b53_switch_remove(dev); + + return 0; +} + +static struct platform_driver b53_srab_driver = { + .probe = b53_srab_probe, + .remove = b53_srab_remove, + .driver = { + .name = "b53-srab-switch", + }, +}; + +module_platform_driver(b53_srab_driver); +MODULE_AUTHOR("Hauke Mehrtens "); +MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/ip17xx.c b/target/linux/generic/files-4.9/drivers/net/phy/ip17xx.c new file mode 100644 index 0000000000..85a9617a5d --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/ip17xx.c @@ -0,0 +1,1377 @@ +/* + * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family + * + * Copyright (C) 2008 Patrick Horn + * Copyright (C) 2008, 2010 Martin Mares + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_VLANS 16 +#define MAX_PORTS 9 +#undef DUMP_MII_IO + +typedef struct ip17xx_reg { + u16 p; // phy + u16 m; // mii +} reg; +typedef char bitnum; + +#define NOTSUPPORTED {-1,-1} + +#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) + +struct ip17xx_state; + +/*********** CONSTANTS ***********/ +struct register_mappings { + char *NAME; + u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. + bitnum NUM_PORTS; + bitnum CPU_PORT; + +/* The default VLAN for each port. + Default: 0x0001 for Ports 0,1,2,3 + 0x0002 for Ports 4,5 */ + reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; + +/* These ports are tagged. + Default: 0x00 */ + reg ADD_TAG_REG; + reg REMOVE_TAG_REG; + bitnum ADD_TAG_BIT[MAX_PORTS]; +/* These ports are untagged. + Default: 0x00 (i.e. do not alter any VLAN tags...) + Maybe set to 0 if user disables VLANs. */ + bitnum REMOVE_TAG_BIT[MAX_PORTS]; + +/* Port M and Port N are on the same VLAN. + Default: All ports on all VLANs. */ +// Use register {29, 19+N/2} + reg VLAN_LOOKUP_REG; +// Port 5 uses register {30, 18} but same as odd bits. + reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. + bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; + bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; + +/* This VLAN corresponds to which ports. + Default: 0x2f,0x30,0x3f,0x3f... */ + reg TAG_VLAN_MASK_REG; + bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; + bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; + + int RESET_VAL; + reg RESET_REG; + + reg MODE_REG; + int MODE_VAL; + +/* General flags */ + reg ROUTER_CONTROL_REG; + reg VLAN_CONTROL_REG; + bitnum TAG_VLAN_BIT; + bitnum ROUTER_EN_BIT; + bitnum NUMLAN_GROUPS_MAX; + bitnum NUMLAN_GROUPS_BIT; + + reg MII_REGISTER_EN; + bitnum MII_REGISTER_EN_BIT; + + // set to 1 for 178C, 0 for 175C. + bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. + + // Pointers to functions which manipulate hardware state + int (*update_state)(struct ip17xx_state *state); + int (*set_vlan_mode)(struct ip17xx_state *state); + int (*reset)(struct ip17xx_state *state); +}; + +static int ip175c_update_state(struct ip17xx_state *state); +static int ip175c_set_vlan_mode(struct ip17xx_state *state); +static int ip175c_reset(struct ip17xx_state *state); + +static const struct register_mappings IP178C = { + .NAME = "IP178C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, + {30,9},{30,10},{30,11}, + }, + + .ADD_TAG_REG = {30,12}, + .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, + .REMOVE_TAG_REG = {30,13}, + .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, + + .SIMPLE_VLAN_REGISTERS = 1, + + .VLAN_LOOKUP_REG = {31,0},// +N + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS + .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .TAG_VLAN_MASK_REG = {30,14}, // +N + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, + .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, + + .RESET_VAL = 0x55AA, + .RESET_REG = {30,0}, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = {30,30}, + .ROUTER_EN_BIT = 11, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 8, // {0-2} + + .VLAN_CONTROL_REG = {30,13}, + .TAG_VLAN_BIT = 3, + + .CPU_PORT = 8, + .NUM_PORTS = 9, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175C = { + .NAME = "IP175C", + .MODEL_NO = 0x18, + .VLAN_DEFAULT_TAG_REG = { + {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {29,23}, + .REMOVE_TAG_REG = {29,23}, + .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = {29,19},// +N/2 + .VLAN_LOOKUP_REG_5 = {30,18}, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = {30,1}, // +N/2 + .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, + + .RESET_VAL = 0x175C, + .RESET_REG = {30,0}, + .MODE_VAL = 0x175C, + .MODE_REG = {29,31}, + + .ROUTER_CONTROL_REG = {30,9}, + .ROUTER_EN_BIT = 3, + .NUMLAN_GROUPS_MAX = 8, + .NUMLAN_GROUPS_BIT = 0, // {0-2} + + .VLAN_CONTROL_REG = {30,9}, + .TAG_VLAN_BIT = 7, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + +static const struct register_mappings IP175A = { + .NAME = "IP175A", + .MODEL_NO = 0x05, + .VLAN_DEFAULT_TAG_REG = { + {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED + }, + + .ADD_TAG_REG = {0,23}, + .REMOVE_TAG_REG = {0,23}, + .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, + .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, + + .SIMPLE_VLAN_REGISTERS = 0, + + // Only programmable via EEPROM + .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, + .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, + + .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, + .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, + + .RESET_VAL = -1, + .RESET_REG = NOTSUPPORTED, + .MODE_VAL = 0, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_MAX = -1, + .NUMLAN_GROUPS_BIT = -1, // {0-2} + + .NUM_PORTS = 5, + .CPU_PORT = 4, + + .MII_REGISTER_EN = {0, 18}, + .MII_REGISTER_EN_BIT = 7, + + .update_state = ip175c_update_state, + .set_vlan_mode = ip175c_set_vlan_mode, + .reset = ip175c_reset, +}; + + +static int ip175d_update_state(struct ip17xx_state *state); +static int ip175d_set_vlan_mode(struct ip17xx_state *state); +static int ip175d_reset(struct ip17xx_state *state); + +static const struct register_mappings IP175D = { + .NAME = "IP175D", + .MODEL_NO = 0x18, + + // The IP175D has a completely different interface, so we leave most + // of the registers undefined and switch to different code paths. + + .VLAN_DEFAULT_TAG_REG = { + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, + }, + + .ADD_TAG_REG = NOTSUPPORTED, + .REMOVE_TAG_REG = NOTSUPPORTED, + + .SIMPLE_VLAN_REGISTERS = 0, + + .VLAN_LOOKUP_REG = NOTSUPPORTED, + .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, + .TAG_VLAN_MASK_REG = NOTSUPPORTED, + + .RESET_VAL = 0x175D, + .RESET_REG = {20,2}, + .MODE_REG = NOTSUPPORTED, + + .ROUTER_CONTROL_REG = NOTSUPPORTED, + .ROUTER_EN_BIT = -1, + .NUMLAN_GROUPS_BIT = -1, + + .VLAN_CONTROL_REG = NOTSUPPORTED, + .TAG_VLAN_BIT = -1, + + .NUM_PORTS = 6, + .CPU_PORT = 5, + + .MII_REGISTER_EN = NOTSUPPORTED, + + .update_state = ip175d_update_state, + .set_vlan_mode = ip175d_set_vlan_mode, + .reset = ip175d_reset, +}; + +struct ip17xx_state { + struct switch_dev dev; + struct mii_bus *mii_bus; + bool registered; + + int router_mode; // ROUTER_EN + int vlan_enabled; // TAG_VLAN_EN + struct port_state { + u16 pvid; + unsigned int shareports; + } ports[MAX_PORTS]; + unsigned int add_tag; + unsigned int remove_tag; + int num_vlans; + struct vlan_state { + unsigned int ports; + unsigned int tag; // VLAN tag (IP175D only) + } vlans[MAX_VLANS]; + const struct register_mappings *regs; + reg proc_mii; // phy/reg for the low level register access via swconfig + + char buf[80]; +}; + +#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) + +static int ip_phy_read(struct ip17xx_state *state, int port, int reg) +{ + int val = mdiobus_read(state->mii_bus, port, reg); + if (val < 0) + pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); +#ifdef DUMP_MII_IO + else + pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); +#endif + return val; +} + +static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) +{ + int err; + +#ifdef DUMP_MII_IO + pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); +#endif + err = mdiobus_write(state->mii_bus, port, reg, val); + if (err < 0) + pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); + return err; +} + +static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) +{ + int val = ip_phy_read(state, port, reg); + if (val < 0) + return 0; + return ip_phy_write(state, port, reg, (val & ~mask) | data); +} + +static int getPhy(struct ip17xx_state *state, reg mii) +{ + if (!REG_SUPP(mii)) + return -EFAULT; + return ip_phy_read(state, mii.p, mii.m); +} + +static int setPhy(struct ip17xx_state *state, reg mii, u16 value) +{ + int err; + + if (!REG_SUPP(mii)) + return -EFAULT; + err = ip_phy_write(state, mii.p, mii.m, value); + if (err < 0) + return err; + mdelay(2); + getPhy(state, mii); + return 0; +} + + +/** + * These two macros are to simplify the mapping of logical bits to the bits in hardware. + * NOTE: these macros will return if there is an error! + */ + +#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ + do { \ + int i, val = getPhy((state), (addr)); \ + if (val < 0) \ + return val; \ + (bits) = 0; \ + for (i = 0; i < MAX_PORTS; i++) { \ + if ((bit_lookup)[i] == -1) continue; \ + if (val & (1<<(bit_lookup)[i])) \ + (bits) |= (1<>i)<<(bit_lookup)[i]); \ + } \ + val = setPhy((state), (addr), val); \ + if (val < 0) \ + return val; \ + } while (0) + + +static int get_model(struct ip17xx_state *state) +{ + int id1, id2; + int oui_id, model_no, rev_no, chip_no; + + id1 = ip_phy_read(state, 0, 2); + id2 = ip_phy_read(state, 0, 3); + oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); + model_no = (id2 >> 4) & 0x3f; + rev_no = id2 & 0xf; + pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); + + if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway + return -ENODEV; + + if (model_no == IP175A.MODEL_NO) { + state->regs = &IP175A; + } else if (model_no == IP175C.MODEL_NO) { + /* + * Several models share the same model_no: + * 178C has more PHYs, so we try whether the device responds to a read from PHY5 + * 175D has a new chip ID register + * 175C has neither + */ + if (ip_phy_read(state, 5, 2) == 0x0243) { + state->regs = &IP178C; + } else { + chip_no = ip_phy_read(state, 20, 0); + pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); + if (chip_no == 0x175d) { + state->regs = &IP175D; + } else { + state->regs = &IP175C; + } + } + } else { + pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); + return -EPERM; + } + return 0; +} + +/*** Low-level functions for the older models ***/ + +/** Only set vlan and router flags in the switch **/ +static int ip175c_set_flags(struct ip17xx_state *state) +{ + int val; + + if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { + return 0; + } + + val = getPhy(state, state->regs->ROUTER_CONTROL_REG); + if (val < 0) { + return val; + } + if (state->regs->ROUTER_EN_BIT >= 0) { + if (state->router_mode) { + val |= (1<regs->ROUTER_EN_BIT); + } else { + val &= (~(1<regs->ROUTER_EN_BIT)); + } + } + if (state->regs->TAG_VLAN_BIT >= 0) { + if (state->vlan_enabled) { + val |= (1<regs->TAG_VLAN_BIT); + } else { + val &= (~(1<regs->TAG_VLAN_BIT)); + } + } + if (state->regs->NUMLAN_GROUPS_BIT >= 0) { + val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<regs->NUMLAN_GROUPS_BIT)); + if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { + val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; + } else if (state->num_vlans >= 1) { + val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; + } + } + return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); +} + +/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ +static int ip175c_set_state(struct ip17xx_state *state) +{ + int j; + int i; + SET_PORT_BITS(state, state->add_tag, + state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); + SET_PORT_BITS(state, state->remove_tag, + state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); + + if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { + for (j=0; jregs->NUM_PORTS; j++) { + reg addr; + const bitnum *bit_lookup = (j%2==0)? + state->regs->VLAN_LOOKUP_EVEN_BIT: + state->regs->VLAN_LOOKUP_ODD_BIT; + + addr = state->regs->VLAN_LOOKUP_REG; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + switch (j) { + case 0: + case 1: + break; + case 2: + case 3: + addr.m+=1; + break; + case 4: + addr.m+=2; + break; + case 5: + addr = state->regs->VLAN_LOOKUP_REG_5; + break; + default: + addr.m = -1; // shouldn't get here, but... + break; + } + } + //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); + if (REG_SUPP(addr)) { + SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); + } + } + } + if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { + for (j=0; jregs->TAG_VLAN_MASK_REG; + const bitnum *bit_lookup = (j%2==0)? + state->regs->TAG_VLAN_MASK_EVEN_BIT: + state->regs->TAG_VLAN_MASK_ODD_BIT; + unsigned int vlan_mask; + if (state->regs->SIMPLE_VLAN_REGISTERS) { + addr.m += j; + } else { + addr.m += j/2; + } + vlan_mask = state->vlans[j].ports; + SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); + } + } + + for (i=0; iregs->VLAN_DEFAULT_TAG_REG[i])) { + int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], + state->ports[i].pvid); + if (err < 0) { + return err; + } + } + } + + return ip175c_set_flags(state); +} + +/** + * Uses only the VLAN port mask and the add tag mask to generate the other fields: + * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. + */ +static void ip175c_correct_vlan_state(struct ip17xx_state *state) +{ + int i, j; + state->num_vlans = 0; + for (i=0; ivlans[i].ports != 0) { + state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... + } + } + + for (i=0; iregs->NUM_PORTS; i++) { + unsigned int portmask = (1<vlan_enabled) { + // Share with everybody! + state->ports[i].shareports = (1<regs->NUM_PORTS)-1; + continue; + } + state->ports[i].shareports = portmask; + for (j=0; jvlans[j].ports & portmask) + state->ports[i].shareports |= state->vlans[j].ports; + } + } +} + +static int ip175c_update_state(struct ip17xx_state *state) +{ + ip175c_correct_vlan_state(state); + return ip175c_set_state(state); +} + +static int ip175c_set_vlan_mode(struct ip17xx_state *state) +{ + return ip175c_update_state(state); +} + +static int ip175c_reset(struct ip17xx_state *state) +{ + int err; + + if (REG_SUPP(state->regs->MODE_REG)) { + err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->MODE_REG); + if (err < 0) + return err; + } + + return ip175c_update_state(state); +} + +/*** Low-level functions for IP175D ***/ + +static int ip175d_update_state(struct ip17xx_state *state) +{ + unsigned int filter_mask = 0; + unsigned int ports[16], add[16], rem[16]; + int i, j; + int err = 0; + + for (i = 0; i < 16; i++) { + ports[i] = 0; + add[i] = 0; + rem[i] = 0; + if (!state->vlan_enabled) { + err |= ip_phy_write(state, 22, 14+i, i+1); // default tags + ports[i] = 0x3f; + continue; + } + if (!state->vlans[i].tag) { + // Reset the filter + err |= ip_phy_write(state, 22, 14+i, 0); // tag + continue; + } + filter_mask |= 1 << i; + err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); + ports[i] = state->vlans[i].ports; + for (j = 0; j < 6; j++) { + if (ports[i] & (1 << j)) { + if (state->add_tag & (1 << j)) + add[i] |= 1 << j; + if (state->remove_tag & (1 << j)) + rem[i] |= 1 << j; + } + } + } + + // Port masks, tag adds and removals + for (i = 0; i < 8; i++) { + err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); + err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); + } + err |= ip_phy_write(state, 22, 10, filter_mask); + + // Default VLAN tag for each port + for (i = 0; i < 6; i++) + err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); + + return (err ? -EIO : 0); +} + +static int ip175d_set_vlan_mode(struct ip17xx_state *state) +{ + int i; + int err = 0; + + if (state->vlan_enabled) { + // VLAN classification rules: tag-based VLANs, use VID to classify, + // drop packets that cannot be classified. + err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); + + // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, + // VID=0xfff discarded, admin both tagged and untagged, ingress + // filters enabled. + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + + // Egress rules: IGMP processing off, keep VLAN header off + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } else { + // VLAN classification rules: everything off & clear table + err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); + + // Ingress and egress rules: set to defaults + err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); + err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); + } + + // Reset default VLAN for each port to 0 + for (i = 0; i < 6; i++) + state->ports[i].pvid = 0; + + err |= ip175d_update_state(state); + + return (err ? -EIO : 0); +} + +static int ip175d_reset(struct ip17xx_state *state) +{ + int err = 0; + + // Disable the special tagging mode + err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); + + // Set 802.1q protocol type + err |= ip_phy_write(state, 22, 3, 0x8100); + + state->vlan_enabled = 0; + err |= ip175d_set_vlan_mode(state); + + return (err ? -EIO : 0); +} + +/*** High-level functions ***/ + +static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + return 0; +} + +static void ip17xx_reset_vlan_config(struct ip17xx_state *state) +{ + int i; + + state->remove_tag = (state->vlan_enabled ? ((1<regs->NUM_PORTS)-1) : 0x0000); + state->add_tag = 0x0000; + for (i = 0; i < MAX_VLANS; i++) { + state->vlans[i].ports = 0x0000; + state->vlans[i].tag = (i ? i : 16); + } + for (i = 0; i < MAX_PORTS; i++) + state->ports[i].pvid = 0; +} + +static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int enable; + + enable = val->value.i; + if (state->vlan_enabled == enable) { + // Do not change any state. + return 0; + } + state->vlan_enabled = enable; + + // Otherwise, if we are switching state, set fields to a known default. + ip17xx_reset_vlan_config(state); + + return state->regs->set_vlan_mode(state); +} + +static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int b; + int ind; + unsigned int ports; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + ports = state->vlans[val->port_vlan].ports; + b = 0; + ind = 0; + while (b < MAX_PORTS) { + if (ports&1) { + int istagged = ((state->add_tag >> b) & 1); + val->value.ports[ind].id = b; + val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); + ind++; + } + b++; + ports >>= 1; + } + val->len = ind; + + return 0; +} + +static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int i; + + if (val->port_vlan >= dev->vlans || val->port_vlan < 0) + return -EINVAL; + + state->vlans[val->port_vlan].ports = 0; + for (i = 0; i < val->len; i++) { + unsigned int bitmask = (1<value.ports[i].id); + state->vlans[val->port_vlan].ports |= bitmask; + if (val->value.ports[i].flags & (1<add_tag |= bitmask; + state->remove_tag &= (~bitmask); + } else { + state->add_tag &= (~bitmask); + state->remove_tag |= bitmask; + } + } + + return state->regs->update_state(state); +} + +static int ip17xx_apply(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + + if (REG_SUPP(state->regs->MII_REGISTER_EN)) { + int val = getPhy(state, state->regs->MII_REGISTER_EN); + if (val < 0) { + return val; + } + val |= (1<regs->MII_REGISTER_EN_BIT); + return setPhy(state, state->regs->MII_REGISTER_EN, val); + } + return 0; +} + +static int ip17xx_reset(struct switch_dev *dev) +{ + struct ip17xx_state *state = get_state(dev); + int i, err; + + if (REG_SUPP(state->regs->RESET_REG)) { + err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); + if (err < 0) + return err; + err = getPhy(state, state->regs->RESET_REG); + + /* + * Data sheet specifies reset period to be 2 msec. + * (I don't see any mention of the 2ms delay in the IP178C spec, only + * in IP175C, but it can't hurt.) + */ + mdelay(2); + } + + /* reset switch ports */ + for (i = 0; i < state->regs->NUM_PORTS-1; i++) { + err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); + if (err < 0) + return err; + } + + state->router_mode = 0; + state->vlan_enabled = 0; + ip17xx_reset_vlan_config(state); + + return state->regs->reset(state); +} + +static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + if (state->add_tag & (1<port_vlan)) { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 3; // shouldn't ever happen. + else + val->value.i = 1; + } else { + if (state->remove_tag & (1<port_vlan)) + val->value.i = 0; + else + val->value.i = 2; + } + return 0; +} + +static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + state->add_tag &= ~(1<port_vlan); + state->remove_tag &= ~(1<port_vlan); + + if (val->value.i == 0) + state->remove_tag |= (1<port_vlan); + if (val->value.i == 1) + state->add_tag |= (1<port_vlan); + + return state->regs->update_state(state); +} + +/** Get the current phy address */ +static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.p; + return 0; +} + +/** Set a new phy address for low level access to registers */ +static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.p = (u16)-1; + else + state->proc_mii.p = (u16)new_reg; + return 0; +} + +/** Get the current register number */ +static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + + val->value.i = state->proc_mii.m; + return 0; +} + +/** Set a new register address for low level access to registers */ +static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int new_reg = val->value.i; + + if (new_reg < 0 || new_reg > 31) + state->proc_mii.m = (u16)-1; + else + state->proc_mii.m = (u16)new_reg; + return 0; +} + +/** Get the register content of state->proc_mii */ +static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int retval = -EINVAL; + if (REG_SUPP(state->proc_mii)) + retval = getPhy(state, state->proc_mii); + + if (retval < 0) { + return retval; + } else { + val->value.i = retval; + return 0; + } +} + +/** Write a value to the register defined by phy/reg above */ +static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int myval, err = -EINVAL; + + myval = val->value.i; + if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { + err = setPhy(state, state->proc_mii, (u16)myval); + } + return err; +} + +static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. + return 0; +} + +static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + val->value.i = state->vlans[vlan].tag; + return 0; +} + +static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int vlan = val->port_vlan; + int tag = val->value.i; + + if (vlan < 0 || vlan >= MAX_VLANS) + return -EINVAL; + + if (tag < 0 || tag > 4095) + return -EINVAL; + + state->vlans[vlan].tag = tag; + return state->regs->update_state(state); +} + +static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int ctrl; + int autoneg; + int speed; + if (val->value.i == 100) { + speed = 1; + autoneg = 0; + } else if (val->value.i == 10) { + speed = 0; + autoneg = 0; + } else { + autoneg = 1; + speed = 1; + } + + /* Can't set speed for cpu port */ + if (nr == state->regs->CPU_PORT) + return -EINVAL; + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + if (ctrl < 0) + return -EIO; + + ctrl &= (~(1<<12)); + ctrl &= (~(1<<13)); + ctrl |= (autoneg<<12); + ctrl |= (speed<<13); + + return ip_phy_write(state, nr, 0, ctrl); +} + +static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int nr = val->port_vlan; + int speed, status; + + if (nr == state->regs->CPU_PORT) { + val->value.i = 100; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (status < 0 || speed < 0) + return -EIO; + + if (status & 4) + val->value.i = ((speed & (1<<11)) ? 100 : 10); + else + val->value.i = 0; + + return 0; +} + +static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct ip17xx_state *state = get_state(dev); + int ctrl, speed, status; + int nr = val->port_vlan; + int len; + char *buf = state->buf; // fixed-length at 80. + + if (nr == state->regs->CPU_PORT) { + sprintf(buf, "up, 100 Mbps, cpu port"); + val->value.s = buf; + return 0; + } + + if (nr >= dev->ports || nr < 0) + return -EINVAL; + + ctrl = ip_phy_read(state, nr, 0); + status = ip_phy_read(state, nr, 1); + speed = ip_phy_read(state, nr, 18); + if (ctrl < 0 || status < 0 || speed < 0) + return -EIO; + + if (status & 4) + len = sprintf(buf, "up, %d Mbps, %s duplex", + ((speed & (1<<11)) ? 100 : 10), + ((speed & (1<<10)) ? "full" : "half")); + else + len = sprintf(buf, "down"); + + if (ctrl & (1<<12)) { + len += sprintf(buf+len, ", auto-negotiate"); + if (!(status & (1<<5))) + len += sprintf(buf+len, " (in progress)"); + } else { + len += sprintf(buf+len, ", fixed speed (%d)", + ((ctrl & (1<<13)) ? 100 : 10)); + } + + buf[len] = '\0'; + val->value.s = buf; + return 0; +} + +static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct ip17xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + return 0; +} + +static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct ip17xx_state *state = get_state(dev); + + if (val < 0 || val >= MAX_VLANS) + return -EINVAL; + + state->ports[port].pvid = val; + return state->regs->update_state(state); +} + + +enum Ports { + IP17XX_PORT_STATUS, + IP17XX_PORT_LINK, + IP17XX_PORT_TAGGED, + IP17XX_PORT_PVID, +}; + +enum Globals { + IP17XX_ENABLE_VLAN, + IP17XX_GET_NAME, + IP17XX_REGISTER_PHY, + IP17XX_REGISTER_MII, + IP17XX_REGISTER_VALUE, + IP17XX_REGISTER_ERRNO, +}; + +enum Vlans { + IP17XX_VLAN_TAG, +}; + +static const struct switch_attr ip17xx_global[] = { + [IP17XX_ENABLE_VLAN] = { + .id = IP17XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Flag to enable or disable VLANs and tagging", + .get = ip17xx_get_enable_vlan, + .set = ip17xx_set_enable_vlan, + }, + [IP17XX_GET_NAME] = { + .id = IP17XX_GET_NAME, + .type = SWITCH_TYPE_STRING, + .description = "Returns the type of IC+ chip.", + .name = "name", + .get = ip17xx_read_name, + .set = NULL, + }, + /* jal: added for low level debugging etc. */ + [IP17XX_REGISTER_PHY] = { + .id = IP17XX_REGISTER_PHY, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set PHY (0-4, or 29,30,31)", + .name = "phy", + .get = ip17xx_get_phy, + .set = ip17xx_set_phy, + }, + [IP17XX_REGISTER_MII] = { + .id = IP17XX_REGISTER_MII, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: set MII register number (0-31)", + .name = "reg", + .get = ip17xx_get_reg, + .set = ip17xx_set_reg, + }, + [IP17XX_REGISTER_VALUE] = { + .id = IP17XX_REGISTER_VALUE, + .type = SWITCH_TYPE_INT, + .description = "Direct register access: read/write to register (0-65535)", + .name = "val", + .get = ip17xx_get_val, + .set = ip17xx_set_val, + }, +}; + +static const struct switch_attr ip17xx_vlan[] = { + [IP17XX_VLAN_TAG] = { + .id = IP17XX_VLAN_TAG, + .type = SWITCH_TYPE_INT, + .description = "VLAN ID (0-4095) [IP175D only]", + .name = "vid", + .get = ip17xx_get_tag, + .set = ip17xx_set_tag, + } +}; + +static const struct switch_attr ip17xx_port[] = { + [IP17XX_PORT_STATUS] = { + .id = IP17XX_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Returns Detailed port status", + .name = "status", + .get = ip17xx_get_port_status, + .set = NULL, + }, + [IP17XX_PORT_LINK] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", + .name = "link", + .get = ip17xx_get_port_speed, + .set = ip17xx_set_port_speed, + }, + [IP17XX_PORT_TAGGED] = { + .id = IP17XX_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", + .name = "tagged", + .get = ip17xx_get_tagged, + .set = ip17xx_set_tagged, + }, +}; + +static const struct switch_dev_ops ip17xx_ops = { + .attr_global = { + .attr = ip17xx_global, + .n_attr = ARRAY_SIZE(ip17xx_global), + }, + .attr_port = { + .attr = ip17xx_port, + .n_attr = ARRAY_SIZE(ip17xx_port), + }, + .attr_vlan = { + .attr = ip17xx_vlan, + .n_attr = ARRAY_SIZE(ip17xx_vlan), + }, + + .get_port_pvid = ip17xx_get_pvid, + .set_port_pvid = ip17xx_set_pvid, + .get_vlan_ports = ip17xx_get_ports, + .set_vlan_ports = ip17xx_set_ports, + .apply_config = ip17xx_apply, + .reset_switch = ip17xx_reset, +}; + +static int ip17xx_probe(struct phy_device *pdev) +{ + struct ip17xx_state *state; + struct switch_dev *dev; + int err; + + /* We only attach to PHY 0, but use all available PHYs */ + if (pdev->mdio.addr != 0) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + dev = &state->dev; + + pdev->priv = state; + state->mii_bus = pdev->mdio.bus; + + err = get_model(state); + if (err < 0) + goto error; + + dev->vlans = MAX_VLANS; + dev->cpu_port = state->regs->CPU_PORT; + dev->ports = state->regs->NUM_PORTS; + dev->name = state->regs->NAME; + dev->ops = &ip17xx_ops; + + pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev)); + return 0; + +error: + kfree(state); + return err; +} + +static int ip17xx_config_init(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + struct net_device *dev = pdev->attached_dev; + int err; + + err = register_switch(&state->dev, dev); + if (err < 0) + return err; + + state->registered = true; + ip17xx_reset(&state->dev); + return 0; +} + +static void ip17xx_remove(struct phy_device *pdev) +{ + struct ip17xx_state *state = pdev->priv; + + if (state->registered) + unregister_switch(&state->dev); + kfree(state); +} + +static int ip17xx_config_aneg(struct phy_device *pdev) +{ + return 0; +} + +static int ip17xx_aneg_done(struct phy_device *pdev) +{ + return 1; /* Return any positive value */ +} + +static int ip17xx_update_link(struct phy_device *pdev) +{ + pdev->link = 1; + return 0; +} + +static int ip17xx_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->pause = pdev->asym_pause = 0; + pdev->link = 1; + + return 0; +} + +static struct phy_driver ip17xx_driver[] = { + { + .name = "IC+ IP17xx", + .phy_id = 0x02430c00, + .phy_id_mask = 0x0ffffc00, + .features = PHY_BASIC_FEATURES, + .probe = ip17xx_probe, + .remove = ip17xx_remove, + .config_init = ip17xx_config_init, + .config_aneg = ip17xx_config_aneg, + .aneg_done = ip17xx_aneg_done, + .update_link = ip17xx_update_link, + .read_status = ip17xx_read_status, + } +}; + +module_phy_driver(ip17xx_driver); + +MODULE_AUTHOR("Patrick Horn "); +MODULE_AUTHOR("Felix Fietkau "); +MODULE_AUTHOR("Martin Mares "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.c b/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.c new file mode 100644 index 0000000000..9a689e6c81 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.c @@ -0,0 +1,947 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mvsw61xx.h" + +MODULE_DESCRIPTION("Marvell 88E61xx Switch driver"); +MODULE_AUTHOR("Claudio Leite "); +MODULE_AUTHOR("Nikita Nazarenko "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mvsw61xx"); + +/* + * Register access is done through direct or indirect addressing, + * depending on how the switch is physically connected. + * + * Direct addressing: all port and global registers directly + * accessible via an address/register pair + * + * Indirect addressing: switch is mapped at a single address, + * port and global registers accessible via a single command/data + * register pair + */ + +static int +mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = bus->read(bus, addr, reg); + if ((r & mask) == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static u16 +r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) +{ + u16 ind_addr; + + if (!indirect) + return bus->read(bus, addr, reg); + + /* Indirect read: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address and request read */ + ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); + + /* Wait until it's ready */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Read the requested data */ + return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); +} + +static void +w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, + int reg, u16 val) +{ + u16 ind_addr; + + if (!indirect) { + bus->write(bus, addr, reg, val); + return; + } + + /* Indirect write: First, make sure switch is free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load the data to be written */ + bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); + + /* Wait again for switch to be free */ + mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address, and issue write command */ + ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); +} + +/* swconfig support */ + +static inline u16 +sr16(struct switch_dev *dev, int addr, int reg) +{ + struct mvsw61xx_state *state = get_state(dev); + + return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); +} + +static inline void +sw16(struct switch_dev *dev, int addr, int reg, u16 val) +{ + struct mvsw61xx_state *state = get_state(dev); + + w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); +} + +static int +mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = sr16(dev, addr, reg) & mask; + if (r == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static int +mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg) +{ + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg); + + if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0) + return -ETIMEDOUT; + + return sr16(dev, MV_GLOBAL2REG(SMI_DATA)); +} + +static int +mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val) +{ + sw16(dev, MV_GLOBAL2REG(SMI_DATA), val); + + sw16(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg); + + return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), + MV_INDIRECT_INPROGRESS, 0) < 0; +} + +static int +mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg) +{ + int ret; + + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + ret = mvsw61xx_mdio_read(dev, port, reg); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); + + return ret; +} + +static void +mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg, + u16 val) +{ + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); + mvsw61xx_mdio_write(dev, port, reg, val); + mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); +} + +static int +mvsw61xx_get_port_mask(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + char *buf = state->buf; + int port, len, i; + u16 reg; + + port = val->port_vlan; + reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; + + len = sprintf(buf, "0x%04x: ", reg); + + for (i = 0; i < MV_PORTS; i++) { + if (reg & (1 << i)) + len += sprintf(buf + len, "%d ", i); + else if (i == port) + len += sprintf(buf + len, "(%d) ", i); + } + + val->value.s = buf; + + return 0; +} + +static int +mvsw61xx_get_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->ports[val->port_vlan].qmode; + + return 0; +} + +static int +mvsw61xx_set_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->ports[val->port_vlan].qmode = val->value.i; + + return 0; +} + +static int +mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + *val = state->ports[port].pvid; + + return 0; +} + +static int +mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct mvsw61xx_state *state = get_state(dev); + + if (val < 0 || val >= MV_VLANS) + return -EINVAL; + + state->ports[port].pvid = (u16)val; + + return 0; +} + +static int +mvsw61xx_get_port_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + u16 status, speed; + + status = sr16(dev, MV_PORTREG(STATUS, port)); + + link->link = status & MV_PORT_STATUS_LINK; + if (!link->link) + return 0; + + link->duplex = status & MV_PORT_STATUS_FDX; + + speed = (status & MV_PORT_STATUS_SPEED_MASK) >> + MV_PORT_STATUS_SPEED_SHIFT; + + switch (speed) { + case MV_PORT_STATUS_SPEED_10: + link->speed = SWITCH_PORT_SPEED_10; + break; + case MV_PORT_STATUS_SPEED_100: + link->speed = SWITCH_PORT_SPEED_100; + break; + case MV_PORT_STATUS_SPEED_1000: + link->speed = SWITCH_PORT_SPEED_1000; + break; + } + + return 0; +} + +static int mvsw61xx_get_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, j, mode, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + for (i = 0, j = 0; i < dev->ports; i++) { + if (state->vlans[vno].mask & (1 << i)) { + val->value.ports[j].id = i; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + if (mode == MV_VTUCTL_EGRESS_TAGGED) + val->value.ports[j].flags = + (1 << SWITCH_PORT_FLAG_TAGGED); + else + val->value.ports[j].flags = 0; + + j++; + } + } + + val->len = j; + + return 0; +} + +static int mvsw61xx_set_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode, pno, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].mask = 0; + state->vlans[vno].port_mode = 0; + state->vlans[vno].port_sstate = 0; + + if(state->vlans[vno].vid == 0) + state->vlans[vno].vid = vno; + + for (i = 0; i < val->len; i++) { + pno = val->value.ports[i].id; + + state->vlans[vno].mask |= (1 << pno); + if (val->value.ports[i].flags & + (1 << SWITCH_PORT_FLAG_TAGGED)) + mode = MV_VTUCTL_EGRESS_TAGGED; + else + mode = MV_VTUCTL_EGRESS_UNTAGGED; + + state->vlans[vno].port_mode |= mode << (pno * 4); + state->vlans[vno].port_sstate |= + MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2); + } + + /* + * DISCARD is nonzero, so it must be explicitly + * set on ports not in the VLAN. + */ + for (i = 0; i < dev->ports; i++) + if (!(state->vlans[vno].mask & (1 << i))) + state->vlans[vno].port_mode |= + MV_VTUCTL_DISCARD << (i * 4); + + return 0; +} + +static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (state->vlans[vno].port_based) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (val->value.i == 1) + state->vlans[vno].port_based = true; + else + state->vlans[vno].port_based = false; + + return 0; +} + +static int mvsw61xx_get_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + val->value.i = state->vlans[vno].vid; + + return 0; +} + +static int mvsw61xx_set_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].vid = val->value.i; + + return 0; +} + +static int mvsw61xx_get_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + + return 0; +} + +static int mvsw61xx_set_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw61xx_state *state = get_state(dev); + + state->vlan_enabled = val->value.i; + + return 0; +} + +static int mvsw61xx_vtu_program(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + u16 v1, v2, s1, s2; + int i; + + /* Flush */ + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE); + + /* Write VLAN table */ + for (i = 1; i < dev->vlans; i++) { + if (state->vlans[i].mask == 0 || + state->vlans[i].vid == 0 || + state->vlans[i].port_based == true) + continue; + + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write per-VLAN port state into STU */ + s1 = (u16) (state->vlans[i].port_sstate & 0xffff); + s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), s1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), s2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + /* Write VLAN information into VTU */ + v1 = (u16) (state->vlans[i].port_mode & 0xffff); + v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_VID), + MV_VTU_VID_VALID | state->vlans[i].vid); + sw16(dev, MV_GLOBALREG(VTU_SID), i); + sw16(dev, MV_GLOBALREG(VTU_FID), i); + sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); + sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); + mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + } + + return 0; +} + +static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno) +{ + struct mvsw61xx_state *state = get_state(dev); + int i, mode; + + for (i = 0; i < dev->ports; i++) { + if (!(state->vlans[vno].mask & (1 << i))) + continue; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + + if(mode != MV_VTUCTL_EGRESS_TAGGED) + state->ports[i].pvid = state->vlans[vno].vid; + + if (state->vlans[vno].port_based) { + state->ports[i].mask |= state->vlans[vno].mask; + state->ports[i].fdb = vno; + } + else + state->ports[i].qmode = MV_8021Q_MODE_SECURE; + } +} + +static int mvsw61xx_update_state(struct switch_dev *dev) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + if (!state->registered) + return -EINVAL; + + /* + * Set 802.1q-only mode if vlan_enabled is true. + * + * Without this, even if 802.1q is enabled for + * a port/VLAN, it still depends on the port-based + * VLAN mask being set. + * + * With this setting, port-based VLANs are still + * functional, provided the VID is not in the VTU. + */ + reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); + + if (state->vlan_enabled) + reg |= MV_8021Q_VLAN_ONLY; + else + reg &= ~MV_8021Q_VLAN_ONLY; + + sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); + + /* + * Set port-based VLAN masks on each port + * based only on VLAN definitions known to + * the driver (i.e. in state). + * + * This means any pre-existing port mapping is + * wiped out once our driver is initialized. + */ + for (i = 0; i < dev->ports; i++) { + state->ports[i].mask = 0; + state->ports[i].qmode = MV_8021Q_MODE_DISABLE; + } + + for (i = 0; i < dev->vlans; i++) + mvsw61xx_vlan_port_config(dev, i); + + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; + reg |= state->ports[i].pvid; + sw16(dev, MV_PORTREG(VLANID, i), reg); + + state->ports[i].mask &= ~(1 << i); + + /* set default forwarding DB number and port mask */ + reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK; + reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) & + MV_FDB_HI_MASK; + sw16(dev, MV_PORTREG(CONTROL1, i), reg); + + reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) | + state->ports[i].mask; + sw16(dev, MV_PORTREG(VLANMAP, i), reg); + + reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & + ~MV_8021Q_MODE_MASK; + reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; + sw16(dev, MV_PORTREG(CONTROL2, i), reg); + } + + mvsw61xx_vtu_program(dev); + + return 0; +} + +static int mvsw61xx_apply(struct switch_dev *dev) +{ + return mvsw61xx_update_state(dev); +} + +static void mvsw61xx_enable_serdes(struct switch_dev *dev) +{ + int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR); + if (bmcr < 0) + return; + + if (bmcr & BMCR_PDOWN) + mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES, + MV_PAGE_FIBER_SERDES, MII_BMCR, + bmcr & ~BMCR_PDOWN); +} + +static int _mvsw61xx_reset(struct switch_dev *dev, bool full) +{ + struct mvsw61xx_state *state = get_state(dev); + int i; + u16 reg; + + /* Disable all ports before reset */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) & + ~MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; + + sw16(dev, MV_GLOBALREG(CONTROL), reg); + if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL), + MV_CONTROL_RESET, 0) < 0) + return -ETIMEDOUT; + + for (i = 0; i < dev->ports; i++) { + state->ports[i].fdb = 0; + state->ports[i].qmode = 0; + state->ports[i].mask = 0; + state->ports[i].pvid = 0; + + /* Force flow control off */ + reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK; + reg |= MV_PHYCTL_FC_DISABLE; + sw16(dev, MV_PORTREG(PHYCTL, i), reg); + + /* Set port association vector */ + sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); + + /* power up phys */ + if (full && i < 5) { + mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL, + MV_SPEC_MDI_CROSS_AUTO | + MV_SPEC_ENERGY_DETECT | + MV_SPEC_DOWNSHIFT_COUNTER); + mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET | + BMCR_ANENABLE | BMCR_FULLDPLX | + BMCR_SPEED1000); + } + + /* enable SerDes if necessary */ + if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) { + u16 sts = sr16(dev, MV_PORTREG(STATUS, i)); + u16 mode = sts & MV_PORT_STATUS_CMODE_MASK; + + if (mode == MV_PORT_STATUS_CMODE_100BASE_X || + mode == MV_PORT_STATUS_CMODE_1000BASE_X || + mode == MV_PORT_STATUS_CMODE_SGMII) { + mvsw61xx_enable_serdes(dev); + } + } + } + + for (i = 0; i < dev->vlans; i++) { + state->vlans[i].port_based = false; + state->vlans[i].mask = 0; + state->vlans[i].vid = 0; + state->vlans[i].port_mode = 0; + state->vlans[i].port_sstate = 0; + } + + state->vlan_enabled = 0; + + mvsw61xx_update_state(dev); + + /* Re-enable ports */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) | + MV_PORTCTRL_FORWARDING; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + return 0; +} + +static int mvsw61xx_reset(struct switch_dev *dev) +{ + return _mvsw61xx_reset(dev, false); +} + +enum { + MVSW61XX_ENABLE_VLAN, +}; + +enum { + MVSW61XX_VLAN_PORT_BASED, + MVSW61XX_VLAN_ID, +}; + +enum { + MVSW61XX_PORT_MASK, + MVSW61XX_PORT_QMODE, +}; + +static const struct switch_attr mvsw61xx_global[] = { + [MVSW61XX_ENABLE_VLAN] = { + .id = MVSW61XX_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable 802.1q VLAN support", + .get = mvsw61xx_get_enable_vlan, + .set = mvsw61xx_set_enable_vlan, + }, +}; + +static const struct switch_attr mvsw61xx_vlan[] = { + [MVSW61XX_VLAN_PORT_BASED] = { + .id = MVSW61XX_VLAN_PORT_BASED, + .type = SWITCH_TYPE_INT, + .name = "port_based", + .description = "Use port-based (non-802.1q) VLAN only", + .get = mvsw61xx_get_vlan_port_based, + .set = mvsw61xx_set_vlan_port_based, + }, + [MVSW61XX_VLAN_ID] = { + .id = MVSW61XX_VLAN_ID, + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "Get/set VLAN ID", + .get = mvsw61xx_get_vid, + .set = mvsw61xx_set_vid, + }, +}; + +static const struct switch_attr mvsw61xx_port[] = { + [MVSW61XX_PORT_MASK] = { + .id = MVSW61XX_PORT_MASK, + .type = SWITCH_TYPE_STRING, + .description = "Port-based VLAN mask", + .name = "mask", + .get = mvsw61xx_get_port_mask, + .set = NULL, + }, + [MVSW61XX_PORT_QMODE] = { + .id = MVSW61XX_PORT_QMODE, + .type = SWITCH_TYPE_INT, + .description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", + .name = "qmode", + .get = mvsw61xx_get_port_qmode, + .set = mvsw61xx_set_port_qmode, + }, +}; + +static const struct switch_dev_ops mvsw61xx_ops = { + .attr_global = { + .attr = mvsw61xx_global, + .n_attr = ARRAY_SIZE(mvsw61xx_global), + }, + .attr_vlan = { + .attr = mvsw61xx_vlan, + .n_attr = ARRAY_SIZE(mvsw61xx_vlan), + }, + .attr_port = { + .attr = mvsw61xx_port, + .n_attr = ARRAY_SIZE(mvsw61xx_port), + }, + .get_port_link = mvsw61xx_get_port_link, + .get_port_pvid = mvsw61xx_get_port_pvid, + .set_port_pvid = mvsw61xx_set_port_pvid, + .get_vlan_ports = mvsw61xx_get_vlan_ports, + .set_vlan_ports = mvsw61xx_set_vlan_ports, + .apply_config = mvsw61xx_apply, + .reset_switch = mvsw61xx_reset, +}; + +/* end swconfig stuff */ + +static int mvsw61xx_probe(struct platform_device *pdev) +{ + struct mvsw61xx_state *state; + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio; + char *model_str; + u32 val; + int err; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mdio = of_parse_phandle(np, "mii-bus", 0); + if (!mdio) { + dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); + err = -ENODEV; + goto out_err; + } + + state->bus = of_mdio_find_bus(mdio); + if (!state->bus) { + dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); + err = -ENODEV; + goto out_err; + } + + state->is_indirect = of_property_read_bool(np, "is-indirect"); + + if (state->is_indirect) { + if (of_property_read_u32(np, "reg", &val)) { + dev_err(&pdev->dev, "Switch address not specified\n"); + err = -ENODEV; + goto out_err; + } + + state->base_addr = val; + } else { + state->base_addr = MV_BASE; + } + + state->model = r16(state->bus, state->is_indirect, state->base_addr, + MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + + switch(state->model) { + case MV_IDENT_VALUE_6171: + model_str = MV_IDENT_STR_6171; + break; + case MV_IDENT_VALUE_6172: + model_str = MV_IDENT_STR_6172; + break; + case MV_IDENT_VALUE_6176: + model_str = MV_IDENT_STR_6176; + break; + case MV_IDENT_VALUE_6352: + model_str = MV_IDENT_STR_6352; + break; + default: + dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n", + state->base_addr); + err = -ENODEV; + goto out_err; + } + + platform_set_drvdata(pdev, state); + dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str, + state->bus->id, state->base_addr); + + dev_info(&pdev->dev, "Using %sdirect addressing\n", + (state->is_indirect ? "in" : "")); + + if (of_property_read_u32(np, "cpu-port-0", &val)) { + dev_err(&pdev->dev, "CPU port not set\n"); + err = -ENODEV; + goto out_err; + } + + state->cpu_port0 = val; + + if (!of_property_read_u32(np, "cpu-port-1", &val)) + state->cpu_port1 = val; + else + state->cpu_port1 = -1; + + state->dev.vlans = MV_VLANS; + state->dev.cpu_port = state->cpu_port0; + state->dev.ports = MV_PORTS; + state->dev.name = model_str; + state->dev.ops = &mvsw61xx_ops; + state->dev.alias = dev_name(&pdev->dev); + + _mvsw61xx_reset(&state->dev, true); + + err = register_switch(&state->dev, NULL); + if (err < 0) + goto out_err; + + state->registered = true; + + return 0; +out_err: + kfree(state); + return err; +} + +static int +mvsw61xx_remove(struct platform_device *pdev) +{ + struct mvsw61xx_state *state = platform_get_drvdata(pdev); + + if (state->registered) + unregister_switch(&state->dev); + + kfree(state); + + return 0; +} + +static const struct of_device_id mvsw61xx_match[] = { + { .compatible = "marvell,88e6171" }, + { .compatible = "marvell,88e6172" }, + { .compatible = "marvell,88e6176" }, + { .compatible = "marvell,88e6352" }, + { } +}; +MODULE_DEVICE_TABLE(of, mvsw61xx_match); + +static struct platform_driver mvsw61xx_driver = { + .probe = mvsw61xx_probe, + .remove = mvsw61xx_remove, + .driver = { + .name = "mvsw61xx", + .of_match_table = of_match_ptr(mvsw61xx_match), + .owner = THIS_MODULE, + }, +}; + +static int __init mvsw61xx_module_init(void) +{ + return platform_driver_register(&mvsw61xx_driver); +} +late_initcall(mvsw61xx_module_init); + +static void __exit mvsw61xx_module_exit(void) +{ + platform_driver_unregister(&mvsw61xx_driver); +} +module_exit(mvsw61xx_module_exit); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.h b/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.h new file mode 100644 index 0000000000..a07b09cd14 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/mvsw61xx.h @@ -0,0 +1,292 @@ +/* + * Marvell 88E61xx switch driver + * + * Copyright (c) 2014 Claudio Leite + * Copyright (c) 2014 Nikita Nazarenko + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __MVSW61XX_H +#define __MVSW61XX_H + +#define MV_PORTS 7 +#define MV_PORTS_MASK ((1 << MV_PORTS) - 1) + +#define MV_BASE 0x10 + +#define MV_SWITCHPORT_BASE 0x10 +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xb) + +#define MV_VLANS 64 + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_PHYCTL = 0x01, + MV_PORT_JAMCTL = 0x02, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_CONTROL1 = 0x05, + MV_PORT_VLANMAP = 0x06, + MV_PORT_VLANID = 0x07, + MV_PORT_CONTROL2 = 0x08, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RX_DISCARD_LOW = 0x10, + MV_PORT_RX_DISCARD_HIGH = 0x11, + MV_PORT_IN_FILTERED = 0x12, + MV_PORT_OUT_ACCEPTED = 0x13, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORT_STATUS_FDX = (1 << 10), + MV_PORT_STATUS_LINK = (1 << 11), +}; + +enum { + MV_PORT_STATUS_CMODE_100BASE_X = 0x8, + MV_PORT_STATUS_CMODE_1000BASE_X = 0x9, + MV_PORT_STATUS_CMODE_SGMII = 0xa, +}; + +#define MV_PORT_STATUS_CMODE_MASK 0xf + +enum { + MV_PORT_STATUS_SPEED_10 = 0x00, + MV_PORT_STATUS_SPEED_100 = 0x01, + MV_PORT_STATUS_SPEED_1000 = 0x02, +}; +#define MV_PORT_STATUS_SPEED_SHIFT 8 +#define MV_PORT_STATUS_SPEED_MASK (3 << 8) + +enum { + MV_PORTCTRL_DISABLED = (0 << 0), + MV_PORTCTRL_BLOCKING = (1 << 0), + MV_PORTCTRL_LEARNING = (2 << 0), + MV_PORTCTRL_FORWARDING = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), + MV_PORTCTRL_EGRESS = (1 << 12), +}; + +#define MV_PHYCTL_FC_MASK (3 << 6) + +enum { + MV_PHYCTL_FC_ENABLE = (3 << 6), + MV_PHYCTL_FC_DISABLE = (1 << 6), +}; + +enum { + MV_8021Q_EGRESS_UNMODIFIED = 0x00, + MV_8021Q_EGRESS_UNTAGGED = 0x01, + MV_8021Q_EGRESS_TAGGED = 0x02, + MV_8021Q_EGRESS_ADDTAG = 0x03, +}; + +#define MV_8021Q_MODE_SHIFT 10 +#define MV_8021Q_MODE_MASK (0x3 << MV_8021Q_MODE_SHIFT) + +enum { + MV_8021Q_MODE_DISABLE = 0x00, + MV_8021Q_MODE_FALLBACK = 0x01, + MV_8021Q_MODE_CHECK = 0x02, + MV_8021Q_MODE_SECURE = 0x03, +}; + +enum { + MV_8021Q_VLAN_ONLY = (1 << 15), +}; + +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_ATU_FID0 = 0x01, + MV_SWITCH_ATU_FID1 = 0x02, + MV_SWITCH_ATU_SID = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, + MV_SWITCH_GLOBAL = 0x1b, + MV_SWITCH_GLOBAL2 = 0x1c, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), + MV_SWITCHCTL_PHYIE = (1 << 1), + MV_SWITCHCTL_ATUDONE = (1 << 2), + MV_SWITCHCTL_ATUIE = (1 << 3), + MV_SWITCHCTL_CTRMODE = (1 << 8), + MV_SWITCHCTL_RELOAD = (1 << 9), + MV_SWITCHCTL_MSIZE = (1 << 10), + MV_SWITCHCTL_DROP = (1 << 13), +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU = (6 << 12), + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +enum { + MV_GLOBAL_STATUS = 0x00, + MV_GLOBAL_ATU_FID = 0x01, + MV_GLOBAL_VTU_FID = 0x02, + MV_GLOBAL_VTU_SID = 0x03, + MV_GLOBAL_CONTROL = 0x04, + MV_GLOBAL_VTU_OP = 0x05, + MV_GLOBAL_VTU_VID = 0x06, + MV_GLOBAL_VTU_DATA1 = 0x07, + MV_GLOBAL_VTU_DATA2 = 0x08, + MV_GLOBAL_VTU_DATA3 = 0x09, + MV_GLOBAL_CONTROL2 = 0x1c, +}; +#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type + +enum { + MV_GLOBAL2_SMI_OP = 0x18, + MV_GLOBAL2_SMI_DATA = 0x19, + MV_GLOBAL2_SDET_POLARITY = 0x1d, +}; +#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type + +enum { + MV_VTU_VID_VALID = (1 << 12), +}; + +enum { + MV_VTUOP_PURGE = (1 << 12), + MV_VTUOP_LOAD = (3 << 12), + MV_VTUOP_INPROGRESS = (1 << 15), + MV_VTUOP_STULOAD = (5 << 12), + MV_VTUOP_VTU_GET_NEXT = (4 << 12), + MV_VTUOP_STU_GET_NEXT = (6 << 12), + MV_VTUOP_GET_VIOLATION = (7 << 12), +}; + +enum { + MV_CONTROL_RESET = (1 << 15), + MV_CONTROL_PPU_ENABLE = (1 << 14), +}; + +enum { + MV_VTUCTL_EGRESS_UNMODIFIED = (0 << 0), + MV_VTUCTL_EGRESS_UNTAGGED = (1 << 0), + MV_VTUCTL_EGRESS_TAGGED = (2 << 0), + MV_VTUCTL_DISCARD = (3 << 0), +}; + +enum { + MV_STUCTL_STATE_DISABLED = (0 << 0), + MV_STUCTL_STATE_BLOCKING = (1 << 0), + MV_STUCTL_STATE_LEARNING = (2 << 0), + MV_STUCTL_STATE_FORWARDING = (3 << 0), +}; + +enum { + MV_INDIRECT_REG_CMD = 0, + MV_INDIRECT_REG_DATA = 1, +}; + +enum { + MV_INDIRECT_INPROGRESS = 0x8000, + MV_INDIRECT_WRITE = 0x9400, + MV_INDIRECT_READ = 0x9800, +}; +#define MV_INDIRECT_ADDR_S 5 + +#define MV_IDENT_MASK 0xfff0 + +#define MV_IDENT_VALUE_6171 0x1710 +#define MV_IDENT_STR_6171 "MV88E6171" + +#define MV_IDENT_VALUE_6172 0x1720 +#define MV_IDENT_STR_6172 "MV88E6172" + +#define MV_IDENT_VALUE_6176 0x1760 +#define MV_IDENT_STR_6176 "MV88E6176" + +#define MV_IDENT_VALUE_6352 0x3520 +#define MV_IDENT_STR_6352 "MV88E6352" + +#define MV_PVID_MASK 0x0fff + +#define MV_FDB_HI_MASK 0x00ff +#define MV_FDB_LO_MASK 0xf000 +#define MV_FDB_HI_SHIFT 4 +#define MV_FDB_LO_SHIFT 12 + +/* Marvell Specific PHY register */ +#define MII_MV_SPEC_CTRL 16 +enum { + MV_SPEC_MDI_CROSS_AUTO = (0x6 << 4), + MV_SPEC_ENERGY_DETECT = (0x3 << 8), + MV_SPEC_DOWNSHIFT_COUNTER = (0x3 << 12), +}; + +#define MII_MV_PAGE 22 + +#define MV_REG_FIBER_SERDES 0xf +#define MV_PAGE_FIBER_SERDES 0x1 + +struct mvsw61xx_state { + struct switch_dev dev; + struct mii_bus *bus; + int base_addr; + u16 model; + + bool registered; + bool is_indirect; + + int cpu_port0; + int cpu_port1; + + int vlan_enabled; + struct port_state { + u16 fdb; + u16 pvid; + u16 mask; + u8 qmode; + } ports[MV_PORTS]; + + struct vlan_state { + bool port_based; + + u16 mask; + u16 vid; + u32 port_mode; + u32 port_sstate; + } vlans[MV_VLANS]; + + char buf[128]; +}; + +#define get_state(_dev) container_of((_dev), struct mvsw61xx_state, dev) + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.c b/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.c new file mode 100644 index 0000000000..043978feb9 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.c @@ -0,0 +1,444 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "mvswitch.h" + +/* Undefine this to use trailer mode instead. + * I don't know if header mode works with all chips */ +#define HEADER_MODE 1 + +MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); +MODULE_AUTHOR("Felix Fietkau"); +MODULE_LICENSE("GPL"); + +#define MVSWITCH_MAGIC 0x88E6060 + +struct mvswitch_priv { + netdev_features_t orig_features; + u8 vlans[16]; +}; + +#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) + +static inline u16 +r16(struct phy_device *phydev, int addr, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, addr, reg); +} + +static inline void +w16(struct phy_device *phydev, int addr, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, addr, reg, val); +} + + +static struct sk_buff * +mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + char *buf = NULL; + u16 vid; + + priv = dev->phy_ptr; + if (unlikely(!priv)) + goto error; + + if (unlikely(skb->len < 16)) + goto error; + +#ifdef HEADER_MODE + if (__vlan_hwaccel_get_tag(skb, &vid)) + goto error; + + if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { + if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) + goto error_expand; + if (skb->len < 62) + skb->len = 62; + } + buf = skb_push(skb, MV_HEADER_SIZE); +#else + if (__vlan_get_tag(skb, &vid)) + goto error; + + if (unlikely((vid > 15 || !priv->vlans[vid]))) + goto error; + + if (skb->len <= 64) { + if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) + goto error_expand; + + buf = skb->data + 64; + skb->len = 64 + MV_TRAILER_SIZE; + } else { + if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { + if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) + goto error_expand; + } + buf = skb_put(skb, 4); + } + + /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ + memmove(skb->data + 4, skb->data, 12); + skb->data += 4; + skb->len -= 4; + skb->mac_header += 4; +#endif + + if (!buf) + goto error; + + +#ifdef HEADER_MODE + /* prepend the tag */ + *((__be16 *) buf) = cpu_to_be16( + ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | + ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) + ); +#else + /* append the tag */ + *((__be32 *) buf) = cpu_to_be32(( + (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | + ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) + )); +#endif + + return skb; + +error_expand: + if (net_ratelimit()) + printk("%s: failed to expand/update skb for the switch\n", dev->name); + +error: + /* any errors? drop the packet! */ + dev_kfree_skb_any(skb); + return NULL; +} + +static void +mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) +{ + struct mvswitch_priv *priv; + unsigned char *buf; + int vlan = -1; + int i; + + priv = dev->phy_ptr; + if (WARN_ON_ONCE(!priv)) + return; + +#ifdef HEADER_MODE + buf = skb->data; + skb_pull(skb, MV_HEADER_SIZE); +#else + buf = skb->data + skb->len - MV_TRAILER_SIZE; + if (buf[0] != 0x80) + return; +#endif + + /* look for the vlan matching the incoming port */ + for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { + if ((1 << buf[1]) & priv->vlans[i]) + vlan = i; + } + + if (vlan == -1) + return; + + __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); +} + + +static int +mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = r16(pdev, addr, reg) & mask; + if (r == val) + return 0; + } while(--i > 0); + return -ETIMEDOUT; +} + +static int +mvswitch_config_init(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + u8 vlmap = 0; + int i; + + if (!dev) + return -EINVAL; + + printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); + pdev->supported = ADVERTISED_100baseT_Full; + pdev->advertising = ADVERTISED_100baseT_Full; + dev->phy_ptr = priv; + pdev->irq = PHY_POLL; +#ifdef HEADER_MODE + dev->flags |= IFF_PROMISC; +#endif + + /* initialize default vlans */ + for (i = 0; i < MV_PORTS; i++) + priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); + + /* before entering reset, disable all ports */ + for (i = 0; i < MV_PORTS; i++) + w16(pdev, MV_PORTREG(CONTROL, i), 0x00); + + msleep(2); /* wait for the status change to settle in */ + + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); + + i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); + if (i < 0) { + printk("%s: Timeout waiting for the switch to reset.\n", dev->name); + return i; + } + + /* set the ATU flags */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), + MV_ATUCTL_NO_LEARN | + MV_ATUCTL_ATU_1K | + MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ + ); + + /* initialize the cpu port */ + w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), +#ifdef HEADER_MODE + MV_PORTCTRL_HEADER | +#else + MV_PORTCTRL_RXTR | + MV_PORTCTRL_TXTR | +#endif + MV_PORTCTRL_ENABLED + ); + /* wait for the phy change to settle in */ + msleep(2); + for (i = 0; i < MV_PORTS; i++) { + u8 pvid = 0; + int j; + + vlmap = 0; + + /* look for the matching vlan */ + for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { + if (priv->vlans[j] & (1 << i)) { + vlmap = priv->vlans[j]; + pvid = j; + } + } + /* leave port unconfigured if it's not part of a vlan */ + if (!vlmap) + continue; + + /* add the cpu port to the allowed destinations list */ + vlmap |= (1 << MV_CPUPORT); + + /* take port out of its own vlan destination map */ + vlmap &= ~(1 << i); + + /* apply vlan settings */ + w16(pdev, MV_PORTREG(VLANMAP, i), + MV_PORTVLAN_PORTS(vlmap) | + MV_PORTVLAN_ID(i) + ); + + /* re-enable port */ + w16(pdev, MV_PORTREG(CONTROL, i), + MV_PORTCTRL_ENABLED + ); + } + + w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), + MV_PORTVLAN_ID(MV_CPUPORT) + ); + + /* set the port association vector */ + for (i = 0; i <= MV_PORTS; i++) { + w16(pdev, MV_PORTREG(ASSOC, i), + MV_PORTASSOC_PORTS(1 << i) + ); + } + + /* init switch control */ + w16(pdev, MV_SWITCHREG(CTRL), + MV_SWITCHCTL_MSIZE | + MV_SWITCHCTL_DROP + ); + + dev->eth_mangle_rx = mvswitch_mangle_rx; + dev->eth_mangle_tx = mvswitch_mangle_tx; + priv->orig_features = dev->features; + +#ifdef HEADER_MODE + dev->priv_flags |= IFF_NO_IP_ALIGN; + dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; +#else + dev->features |= NETIF_F_HW_VLAN_CTAG_RX; +#endif + + return 0; +} + +static int +mvswitch_read_status(struct phy_device *pdev) +{ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + + /* XXX ugly workaround: we can't force the switch + * to gracefully handle hosts moving from one port to another, + * so we have to regularly clear the ATU database */ + + /* wait for the ATU to become available */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + /* flush the ATU */ + w16(pdev, MV_SWITCHREG(ATU_OP), + MV_ATUOP_INPROGRESS | + MV_ATUOP_FLUSH_ALL + ); + + /* wait for operation to complete */ + mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); + + return 0; +} + +static int +mvswitch_aneg_done(struct phy_device *phydev) +{ + return 1; /* Return any positive value */ +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static void +mvswitch_detach(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + struct net_device *dev = pdev->attached_dev; + + if (!dev) + return; + + dev->phy_ptr = NULL; + dev->eth_mangle_rx = NULL; + dev->eth_mangle_tx = NULL; + dev->features = priv->orig_features; + dev->priv_flags &= ~IFF_NO_IP_ALIGN; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ + struct mvswitch_priv *priv = to_mvsw(pdev); + + kfree(priv); +} + +static int +mvswitch_probe(struct phy_device *pdev) +{ + struct mvswitch_priv *priv; + + priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + pdev->priv = priv; + + return 0; +} + +static int +mvswitch_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + if (dev->mdio.addr != 0x10) + return 0; + + reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) + return 0; + + dev->phy_id = MVSWITCH_MAGIC; + return 0; +} + + +static struct phy_driver mvswitch_driver = { + .name = "Marvell 88E6060", + .phy_id = MVSWITCH_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &mvswitch_probe, + .remove = &mvswitch_remove, + .detach = &mvswitch_detach, + .config_init = &mvswitch_config_init, + .config_aneg = &mvswitch_config_aneg, + .aneg_done = &mvswitch_aneg_done, + .read_status = &mvswitch_read_status, +}; + +static int __init +mvswitch_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); + return phy_driver_register(&mvswitch_driver, THIS_MODULE); +} + +static void __exit +mvswitch_exit(void) +{ + phy_driver_unregister(&mvswitch_driver); +} + +module_init(mvswitch_init); +module_exit(mvswitch_exit); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.h b/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.h new file mode 100644 index 0000000000..ab2a1a126e --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/mvswitch.h @@ -0,0 +1,145 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ +#ifndef __MVSWITCH_H +#define __MVSWITCH_H + +#define MV_HEADER_SIZE 2 +#define MV_HEADER_PORTS_M 0x001f +#define MV_HEADER_PORTS_S 0 +#define MV_HEADER_VLAN_M 0xf000 +#define MV_HEADER_VLAN_S 12 + +#define MV_TRAILER_SIZE 4 +#define MV_TRAILER_PORTS_M 0x1f +#define MV_TRAILER_PORTS_S 16 +#define MV_TRAILER_FLAGS_S 24 +#define MV_TRAILER_OVERRIDE 0x80 + + +#define MV_PORTS 5 +#define MV_WANPORT 4 +#define MV_CPUPORT 5 + +#define MV_BASE 0x10 + +#define MV_PHYPORT_BASE (MV_BASE + 0x0) +#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) +#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xf) + +enum { + MV_PHY_CONTROL = 0x00, + MV_PHY_STATUS = 0x01, + MV_PHY_IDENT0 = 0x02, + MV_PHY_IDENT1 = 0x03, + MV_PHY_ANEG = 0x04, + MV_PHY_LINK_ABILITY = 0x05, + MV_PHY_ANEG_EXPAND = 0x06, + MV_PHY_XMIT_NEXTP = 0x07, + MV_PHY_LINK_NEXTP = 0x08, + MV_PHY_CONTROL1 = 0x10, + MV_PHY_STATUS1 = 0x11, + MV_PHY_INTR_EN = 0x12, + MV_PHY_INTR_STATUS = 0x13, + MV_PHY_INTR_PORT = 0x14, + MV_PHY_RECV_COUNTER = 0x16, + MV_PHY_LED_PARALLEL = 0x16, + MV_PHY_LED_STREAM = 0x17, + MV_PHY_LED_CTRL = 0x18, + MV_PHY_LED_OVERRIDE = 0x19, + MV_PHY_VCT_CTRL = 0x1a, + MV_PHY_VCT_STATUS = 0x1b, + MV_PHY_CONTROL2 = 0x1e +}; +#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_VLANMAP = 0x06, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ + MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ + MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ + MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ + MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ +}; + +#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) +#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) + +#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ + MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ + MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ + MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ + MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ + MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ + MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ + MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU= (6 << 12), + + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +#define MV_IDENT_MASK 0xfff0 +#define MV_IDENT_VALUE 0x0600 + +#endif diff --git a/target/linux/generic/files-4.9/drivers/net/phy/psb6970.c b/target/linux/generic/files-4.9/drivers/net/phy/psb6970.c new file mode 100644 index 0000000000..c1a381c052 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/psb6970.c @@ -0,0 +1,441 @@ +/* + * Lantiq PSB6970 (Tantos) Switch driver + * + * Copyright (c) 2009,2010 Team Embedded. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation. + * + * The switch programming done in this driver follows the + * "Ethernet Traffic Separation using VLAN" Application Note as + * published by Lantiq. + */ + +#include +#include +#include +#include + +#define PSB6970_MAX_VLANS 16 +#define PSB6970_NUM_PORTS 7 +#define PSB6970_DEFAULT_PORT_CPU 6 +#define PSB6970_IS_CPU_PORT(x) ((x) > 4) + +#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) + +/* --- Identification --- */ +#define PSB6970_CI0 0x0100 +#define PSB6970_CI0_MASK 0x000f +#define PSB6970_CI1 0x0101 +#define PSB6970_CI1_VAL 0x2599 +#define PSB6970_CI1_MASK 0xffff + +/* --- VLAN filter table --- */ +#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ +#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ + +#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ +#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ + +/* --- Port registers --- */ +#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ +#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ + +#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ +#define PSB6970_PBVM_VMCE (1 << 8) +#define PSB6970_PBVM_AOVTP (1 << 9) +#define PSB6970_PBVM_VSD (1 << 10) +#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ +#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ + +#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ + +struct psb6970_priv { + struct switch_dev dev; + struct phy_device *phy; + u16 (*read) (struct phy_device* phydev, int reg); + void (*write) (struct phy_device* phydev, int reg, u16 val); + struct mutex reg_mutex; + + /* all fields below are cleared on reset */ + bool vlan; + u16 vlan_id[PSB6970_MAX_VLANS]; + u8 vlan_table[PSB6970_MAX_VLANS]; + u8 vlan_tagged; + u16 pvid[PSB6970_NUM_PORTS]; +}; + +#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) + +static u16 psb6970_mii_read(struct phy_device *phydev, int reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + + return bus->read(bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + + bus->write(bus, PHYADDR(reg), val); +} + +static int +psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan = !!val->value.i; + return 0; +} + +static int +psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan; + return 0; +} + +static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + + /* make sure no invalid PVIDs get set */ + if (vlan >= dev->vlans) + return -EINVAL; + + priv->pvid[port] = vlan; + return 0; +} + +static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ + struct psb6970_priv *priv = to_psb6970(dev); + *vlan = priv->pvid[port]; + return 0; +} + +static int +psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + priv->vlan_id[val->port_vlan] = val->value.i; + return 0; +} + +static int +psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + val->value.i = priv->vlan_id[val->port_vlan]; + return 0; +} + +static struct switch_attr psb6970_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = psb6970_set_vlan, + .get = psb6970_get_vlan, + .max = 1}, +}; + +static struct switch_attr psb6970_port[] = { +}; + +static struct switch_attr psb6970_vlan[] = { + { + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "VLAN ID (0-4094)", + .set = psb6970_set_vid, + .get = psb6970_get_vid, + .max = 4094, + }, +}; + +static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 ports = priv->vlan_table[val->port_vlan]; + int i; + + val->len = 0; + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + struct switch_port *p; + + if (!(ports & (1 << i))) + continue; + + p = &val->value.ports[val->len++]; + p->id = i; + if (priv->vlan_tagged & (1 << i)) + p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + else + p->flags = 0; + } + return 0; +} + +static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct psb6970_priv *priv = to_psb6970(dev); + u8 *vt = &priv->vlan_table[val->port_vlan]; + int i, j; + + *vt = 0; + for (i = 0; i < val->len; i++) { + struct switch_port *p = &val->value.ports[i]; + + if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) + priv->vlan_tagged |= (1 << p->id); + else { + priv->vlan_tagged &= ~(1 << p->id); + priv->pvid[p->id] = val->port_vlan; + + /* make sure that an untagged port does not + * appear in other vlans */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + if (j == val->port_vlan) + continue; + priv->vlan_table[j] &= ~(1 << p->id); + } + } + + *vt |= 1 << p->id; + } + return 0; +} + +static int psb6970_hw_apply(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i, j; + + mutex_lock(&priv->reg_mutex); + + if (priv->vlan) { + /* into the vlan translation unit */ + for (j = 0; j < PSB6970_MAX_VLANS; j++) { + u8 vp = priv->vlan_table[j]; + + if (vp) { + priv->write(priv->phy, PSB6970_VFxL(j), + PSB6970_VFxL_VV | priv->vlan_id[j]); + priv->write(priv->phy, PSB6970_VFxH(j), + ((vp & priv-> + vlan_tagged) << + PSB6970_VFxH_TM_SHIFT) | vp); + } else /* clear VLAN Valid flag for unused vlans */ + priv->write(priv->phy, PSB6970_VFxL(j), 0); + + } + } + + /* update the port destination mask registers and tag settings */ + for (i = 0; i < PSB6970_NUM_PORTS; i++) { + int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; + + if (priv->vlan) { + ec = PSB6970_EC_IFNTE; + dvid = priv->vlan_id[priv->pvid[i]]; + pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; + + if ((i << 1) & priv->vlan_tagged) + pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; + } + + priv->write(priv->phy, PSB6970_PBVM(i), pbvm); + + if (!PSB6970_IS_CPU_PORT(i)) { + priv->write(priv->phy, PSB6970_EC(i), ec); + priv->write(priv->phy, PSB6970_DVID(i), dvid); + } + } + + mutex_unlock(&priv->reg_mutex); + return 0; +} + +static int psb6970_reset_switch(struct switch_dev *dev) +{ + struct psb6970_priv *priv = to_psb6970(dev); + int i; + + mutex_lock(&priv->reg_mutex); + + memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - + offsetof(struct psb6970_priv, vlan)); + + for (i = 0; i < PSB6970_MAX_VLANS; i++) + priv->vlan_id[i] = i; + + mutex_unlock(&priv->reg_mutex); + + return psb6970_hw_apply(dev); +} + +static const struct switch_dev_ops psb6970_ops = { + .attr_global = { + .attr = psb6970_globals, + .n_attr = ARRAY_SIZE(psb6970_globals), + }, + .attr_port = { + .attr = psb6970_port, + .n_attr = ARRAY_SIZE(psb6970_port), + }, + .attr_vlan = { + .attr = psb6970_vlan, + .n_attr = ARRAY_SIZE(psb6970_vlan), + }, + .get_port_pvid = psb6970_get_pvid, + .set_port_pvid = psb6970_set_pvid, + .get_vlan_ports = psb6970_get_ports, + .set_vlan_ports = psb6970_set_ports, + .apply_config = psb6970_hw_apply, + .reset_switch = psb6970_reset_switch, +}; + +static int psb6970_config_init(struct phy_device *pdev) +{ + struct psb6970_priv *priv; + struct net_device *dev = pdev->attached_dev; + struct switch_dev *swdev; + int ret; + + priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->phy = pdev; + + if (pdev->mdio.addr == 0) + printk(KERN_INFO "%s: psb6970 switch driver attached.\n", + pdev->attached_dev->name); + + if (pdev->mdio.addr != 0) { + kfree(priv); + return 0; + } + + pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; + + mutex_init(&priv->reg_mutex); + priv->read = psb6970_mii_read; + priv->write = psb6970_mii_write; + + pdev->priv = priv; + + swdev = &priv->dev; + swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; + swdev->ops = &psb6970_ops; + + swdev->name = "Lantiq PSB6970"; + swdev->vlans = PSB6970_MAX_VLANS; + swdev->ports = PSB6970_NUM_PORTS; + + if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { + kfree(priv); + goto done; + } + + ret = psb6970_reset_switch(&priv->dev); + if (ret) { + kfree(priv); + goto done; + } + + dev->phy_ptr = priv; + +done: + return ret; +} + +static int psb6970_read_status(struct phy_device *phydev) +{ + phydev->speed = SPEED_100; + phydev->duplex = DUPLEX_FULL; + phydev->link = 1; + + phydev->state = PHY_RUNNING; + netif_carrier_on(phydev->attached_dev); + phydev->adjust_link(phydev->attached_dev); + + return 0; +} + +static int psb6970_config_aneg(struct phy_device *phydev) +{ + return 0; +} + +static int psb6970_probe(struct phy_device *pdev) +{ + return 0; +} + +static void psb6970_remove(struct phy_device *pdev) +{ + struct psb6970_priv *priv = pdev->priv; + + if (!priv) + return; + + if (pdev->mdio.addr == 0) + unregister_switch(&priv->dev); + kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ + struct mii_bus *bus = dev->mdio.bus; + u16 reg; + + /* look for the switch on the bus */ + reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; + if (reg != PSB6970_CI1_VAL) + return 0; + + dev->phy_id = (reg << 16); + dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; + + return 0; +} + +static struct phy_driver psb6970_driver = { + .name = "Lantiq PSB6970", + .phy_id = PSB6970_CI1_VAL << 16, + .phy_id_mask = 0xffff0000, + .features = PHY_BASIC_FEATURES, + .probe = psb6970_probe, + .remove = psb6970_remove, + .config_init = &psb6970_config_init, + .config_aneg = &psb6970_config_aneg, + .read_status = &psb6970_read_status, +}; + +int __init psb6970_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); + return phy_driver_register(&psb6970_driver, THIS_MODULE); +} + +module_init(psb6970_init); + +void __exit psb6970_exit(void) +{ + phy_driver_unregister(&psb6970_driver); +} + +module_exit(psb6970_exit); + +MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); +MODULE_AUTHOR("Ithamar R. Adema "); +MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8306.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8306.c new file mode 100644 index 0000000000..6d09c1063c --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8306.c @@ -0,0 +1,1066 @@ +/* + * rtl8306.c: RTL8306S switch driver + * + * Copyright (C) 2009 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG 1 + +/* Global (PHY0) */ +#define RTL8306_REG_PAGE 16 +#define RTL8306_REG_PAGE_LO (1 << 15) +#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ + +#define RTL8306_NUM_VLANS 16 +#define RTL8306_NUM_PORTS 6 +#define RTL8306_PORT_CPU 5 +#define RTL8306_NUM_PAGES 4 +#define RTL8306_NUM_REGS 32 + +#define RTL_NAME_S "RTL8306S" +#define RTL_NAME_SD "RTL8306SD" +#define RTL_NAME_SDM "RTL8306SDM" +#define RTL_NAME_UNKNOWN "RTL8306(unknown)" + +#define RTL8306_MAGIC 0x8306 + +static LIST_HEAD(phydevs); + +struct rtl_priv { + struct list_head list; + struct switch_dev dev; + int page; + int type; + int do_cpu; + struct mii_bus *bus; + char hwname[sizeof(RTL_NAME_UNKNOWN)]; + bool fixup; +}; + +struct rtl_phyregs { + int nway; + int speed; + int duplex; +}; + +#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) + +enum { + RTL_TYPE_S, + RTL_TYPE_SD, + RTL_TYPE_SDM, +}; + +struct rtl_reg { + int page; + int phy; + int reg; + int bits; + int shift; + int inverted; +}; + +#define RTL_VLAN_REGOFS(name) \ + (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) + +#define RTL_PORT_REGOFS(name) \ + (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) + +#define RTL_PORT_REG(id, reg) \ + (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) + +#define RTL_VLAN_REG(id, reg) \ + (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) + +#define RTL_GLOBAL_REGATTR(reg) \ + .id = RTL_REG_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = 0, \ + .set = rtl_attr_set_int, \ + .get = rtl_attr_get_int + +#define RTL_PORT_REGATTR(reg) \ + .id = RTL_REG_PORT0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_PORT_REGOFS(reg), \ + .set = rtl_attr_set_port_int, \ + .get = rtl_attr_get_port_int + +#define RTL_VLAN_REGATTR(reg) \ + .id = RTL_REG_VLAN0_##reg, \ + .type = SWITCH_TYPE_INT, \ + .ofs = RTL_VLAN_REGOFS(reg), \ + .set = rtl_attr_set_vlan_int, \ + .get = rtl_attr_get_vlan_int + +enum rtl_regidx { + RTL_REG_CHIPID, + RTL_REG_CHIPVER, + RTL_REG_CHIPTYPE, + RTL_REG_CPUPORT, + + RTL_REG_EN_CPUPORT, + RTL_REG_EN_TAG_OUT, + RTL_REG_EN_TAG_CLR, + RTL_REG_EN_TAG_IN, + RTL_REG_TRAP_CPU, + RTL_REG_CPU_LINKUP, + RTL_REG_TRUNK_PORTSEL, + RTL_REG_EN_TRUNK, + RTL_REG_RESET, + + RTL_REG_VLAN_ENABLE, + RTL_REG_VLAN_FILTER, + RTL_REG_VLAN_TAG_ONLY, + RTL_REG_VLAN_TAG_AWARE, +#define RTL_VLAN_ENUM(id) \ + RTL_REG_VLAN##id##_VID, \ + RTL_REG_VLAN##id##_PORTMASK + RTL_VLAN_ENUM(0), + RTL_VLAN_ENUM(1), + RTL_VLAN_ENUM(2), + RTL_VLAN_ENUM(3), + RTL_VLAN_ENUM(4), + RTL_VLAN_ENUM(5), + RTL_VLAN_ENUM(6), + RTL_VLAN_ENUM(7), + RTL_VLAN_ENUM(8), + RTL_VLAN_ENUM(9), + RTL_VLAN_ENUM(10), + RTL_VLAN_ENUM(11), + RTL_VLAN_ENUM(12), + RTL_VLAN_ENUM(13), + RTL_VLAN_ENUM(14), + RTL_VLAN_ENUM(15), +#define RTL_PORT_ENUM(id) \ + RTL_REG_PORT##id##_PVID, \ + RTL_REG_PORT##id##_NULL_VID_REPLACE, \ + RTL_REG_PORT##id##_NON_PVID_DISCARD, \ + RTL_REG_PORT##id##_VID_INSERT, \ + RTL_REG_PORT##id##_TAG_INSERT, \ + RTL_REG_PORT##id##_LINK, \ + RTL_REG_PORT##id##_SPEED, \ + RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ + RTL_REG_PORT##id##_DUPLEX, \ + RTL_REG_PORT##id##_RXEN, \ + RTL_REG_PORT##id##_TXEN + RTL_PORT_ENUM(0), + RTL_PORT_ENUM(1), + RTL_PORT_ENUM(2), + RTL_PORT_ENUM(3), + RTL_PORT_ENUM(4), + RTL_PORT_ENUM(5), +}; + +static const struct rtl_reg rtl_regs[] = { + [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, + [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, + [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, + + /* CPU port number */ + [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, + /* Enable CPU port function */ + [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, + /* Enable CPU port tag insertion */ + [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, + /* Enable CPU port tag removal */ + [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, + /* Enable CPU port tag checking */ + [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, + [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, + [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, + [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, + + [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, + [RTL_REG_CPU_LINKUP] = { 0, 6, 22, 1, 15, 0 }, + + [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, + [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, + [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, + [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, + +#define RTL_VLAN_REGS(id, phy, page, regofs) \ + [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ + [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } + RTL_VLAN_REGS( 0, 0, 0, 0), + RTL_VLAN_REGS( 1, 1, 0, 0), + RTL_VLAN_REGS( 2, 2, 0, 0), + RTL_VLAN_REGS( 3, 3, 0, 0), + RTL_VLAN_REGS( 4, 4, 0, 0), + RTL_VLAN_REGS( 5, 0, 1, 2), + RTL_VLAN_REGS( 6, 1, 1, 2), + RTL_VLAN_REGS( 7, 2, 1, 2), + RTL_VLAN_REGS( 8, 3, 1, 2), + RTL_VLAN_REGS( 9, 4, 1, 2), + RTL_VLAN_REGS(10, 0, 1, 4), + RTL_VLAN_REGS(11, 1, 1, 4), + RTL_VLAN_REGS(12, 2, 1, 4), + RTL_VLAN_REGS(13, 3, 1, 4), + RTL_VLAN_REGS(14, 4, 1, 4), + RTL_VLAN_REGS(15, 0, 1, 6), + +#define REG_PORT_SETTING(port, phy) \ + [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ + [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ + [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ + [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ + [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ + [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ + [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ + [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } + + REG_PORT_SETTING(0, 0), + REG_PORT_SETTING(1, 1), + REG_PORT_SETTING(2, 2), + REG_PORT_SETTING(3, 3), + REG_PORT_SETTING(4, 4), + REG_PORT_SETTING(5, 6), + +#define REG_PORT_PVID(phy, page, regofs) \ + { page, phy, 24 + regofs, 4, 12, 0 } + [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), + [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), + [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), + [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), + [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), + [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), +}; + + +static inline void +rtl_set_page(struct rtl_priv *priv, unsigned int page) +{ + struct mii_bus *bus = priv->bus; + u16 pgsel; + + if (priv->fixup) + return; + + if (priv->page == page) + return; + + BUG_ON(page > RTL8306_NUM_PAGES); + pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); + pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); + if (page & (1 << 0)) + pgsel |= RTL8306_REG_PAGE_LO; + if (!(page & (1 << 1))) /* bit is inverted */ + pgsel |= RTL8306_REG_PAGE_HI; + bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); +} + +static inline int +rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + bus->write(bus, phy, reg, val); + bus->read(bus, phy, reg); /* flush */ + return 0; +} + +static inline int +rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + + rtl_set_page(priv, page); + return bus->read(bus, phy, reg); +} + +static inline u16 +rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct mii_bus *bus = priv->bus; + u16 r; + + rtl_set_page(priv, page); + r = bus->read(bus, phy, reg); + r &= ~mask; + r |= val; + bus->write(bus, phy, reg, r); + return bus->read(bus, phy, reg); /* flush */ +} + + +static inline int +rtl_get(struct switch_dev *dev, enum rtl_regidx s) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 val; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + if (r->bits == 0) /* unimplemented */ + return 0; + + val = rtl_r16(dev, r->page, r->phy, r->reg); + + if (r->shift > 0) + val >>= r->shift; + + if (r->inverted) + val = ~val; + + val &= (1 << r->bits) - 1; + + return val; +} + +static int +rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) +{ + const struct rtl_reg *r = &rtl_regs[s]; + u16 mask = 0xffff; + + BUG_ON(s >= ARRAY_SIZE(rtl_regs)); + + if (r->bits == 0) /* unimplemented */ + return 0; + + if (r->shift > 0) + val <<= r->shift; + + if (r->inverted) + val = ~val; + + if (r->bits != 16) { + mask = (1 << r->bits) - 1; + mask <<= r->shift; + } + val &= mask; + return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); +} + +static void +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); +} + +static void +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) +{ + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); +} + +static void +rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) +{ + rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); + rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); +} + +static int +rtl_hw_apply(struct switch_dev *dev) +{ + int i; + int trunk_en, trunk_psel; + struct rtl_phyregs port5; + + rtl_phy_save(dev, 5, &port5); + + /* disable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 0); + } + + /* save trunking status */ + trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); + trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); + + /* trunk port 3 and 4 + * XXX: Big WTF, but RealTek seems to do it */ + rtl_set(dev, RTL_REG_EN_TRUNK, 1); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); + + /* execute the software reset */ + rtl_set(dev, RTL_REG_RESET, 1); + + /* wait for the reset to complete, + * but don't wait for too long */ + for (i = 0; i < 10; i++) { + if (rtl_get(dev, RTL_REG_RESET) == 0) + break; + + msleep(1); + } + + /* enable rx/tx from PHYs */ + for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { + rtl_port_set_enable(dev, i, 1); + } + + /* restore trunking settings */ + rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); + rtl_phy_restore(dev, 5, &port5); + + rtl_set(dev, RTL_REG_CPU_LINKUP, 1); + + return 0; +} + +static void +rtl_hw_init(struct switch_dev *dev) +{ + struct rtl_priv *priv = to_rtl(dev); + int cpu_mask = 1 << dev->cpu_port; + int i; + + rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); + rtl_set(dev, RTL_REG_VLAN_FILTER, 0); + rtl_set(dev, RTL_REG_EN_TRUNK, 0); + rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); + + /* initialize cpu port settings */ + if (priv->do_cpu) { + rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); + rtl_set(dev, RTL_REG_EN_CPUPORT, 1); + } else { + rtl_set(dev, RTL_REG_CPUPORT, 7); + rtl_set(dev, RTL_REG_EN_CPUPORT, 0); + } + rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); + rtl_set(dev, RTL_REG_EN_TAG_IN, 0); + rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); + + /* reset all vlans */ + for (i = 0; i < RTL8306_NUM_VLANS; i++) { + rtl_set(dev, RTL_VLAN_REG(i, VID), i); + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); + } + + /* default to port isolation */ + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + unsigned long mask; + + if ((1 << i) == cpu_mask) + mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ + else + mask = cpu_mask | (1 << i); + + rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); + rtl_set(dev, RTL_PORT_REG(i, PVID), i); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); + } + rtl_hw_apply(dev); +} + +#ifdef DEBUG +static int +rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + priv->do_cpu = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + val->value.i = priv->do_cpu; + return 0; +} + +static int +rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + dev->cpu_port = val->value.i; + rtl_hw_init(dev); + return 0; +} + +static int +rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = dev->cpu_port; + return 0; +} +#endif + +static int +rtl_reset(struct switch_dev *dev) +{ + rtl_hw_init(dev); + return 0; +} + +static int +rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; + + if (attr->id >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + if ((attr->max > 0) && (val->value.i > attr->max)) + return -EINVAL; + + /* access to phy register 22 on port 4/5 + * needs phy status save/restore */ + if ((val->port_vlan > 3) && + (rtl_regs[idx].reg == 22) && + (rtl_regs[idx].page == 0)) { + + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, idx, val->value.i); + rtl_phy_restore(dev, val->port_vlan, &port); + } else { + rtl_set(dev, idx, val->value.i); + } + + return 0; +} + +static int +rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + int idx = attr->id + (val->port_vlan * attr->ofs); + + if (idx >= ARRAY_SIZE(rtl_regs)) + return -EINVAL; + + val->value.i = rtl_get(dev, idx); + return 0; +} + +static int +rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= RTL8306_NUM_PORTS) + return -EINVAL; + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link) +{ + if (port >= RTL8306_NUM_PORTS) + return -EINVAL; + + /* in case the link changes from down to up, the register is only updated on read */ + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + if (!link->link) + link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); + + if (!link->link) + return 0; + + link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); + link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + + if (rtl_get(dev, RTL_PORT_REG(port, SPEED))) + link->speed = SWITCH_PORT_SPEED_100; + else + link->speed = SWITCH_PORT_SPEED_10; + + return 0; +} + +static int +rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_set_int(dev, attr, val); +} + +static int +rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + return rtl_attr_get_int(dev, attr, val); +} + +static int +rtl_get_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int i, mask; + + mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + struct switch_port *port; + + if (!(mask & (1 << i))) + continue; + + port = &val->value.ports[val->len]; + port->id = i; + if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port) + port->flags = (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; + int en = val->value.i; + int i; + + rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); + rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); + rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); + if (en) + rtl_set(dev, RTL_REG_VLAN_FILTER, en); + + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (i > 3) + rtl_phy_save(dev, val->port_vlan, &port); + rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); + rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); + rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); + if (i > 3) + rtl_phy_restore(dev, val->port_vlan, &port); + } + rtl_set(dev, RTL_REG_VLAN_ENABLE, en); + + return 0; +} + +static int +rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) +{ + val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE); + return 0; +} + +static int +rtl_set_ports(struct switch_dev *dev, struct switch_val *val) +{ + unsigned int mask = 0; + unsigned int oldmask; + int i; + + for(i = 0; i < val->len; i++) + { + struct switch_port *port = &val->value.ports[i]; + bool tagged = false; + + mask |= (1 << port->id); + + if (port->id == dev->cpu_port) + continue; + + if ((i == dev->cpu_port) || + (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + tagged = true; + + /* fix up PVIDs for added ports */ + if (!tagged) + rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); + + rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); + rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); + } + + oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); + rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); + + /* fix up PVIDs for removed ports, default to last vlan */ + oldmask &= ~mask; + for (i = 0; i < RTL8306_NUM_PORTS; i++) { + if (!(oldmask & (1 << i))) + continue; + + if (i == dev->cpu_port) + continue; + + if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) + rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); + } + + return 0; +} + +static struct switch_attr rtl_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .max = 1, + .set = rtl_set_vlan, + .get = rtl_get_vlan, + }, + { + RTL_GLOBAL_REGATTR(EN_TRUNK), + .name = "trunk", + .description = "Enable port trunking", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), + .name = "trunk_sel", + .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", + .max = 1, + }, +#ifdef DEBUG + { + RTL_GLOBAL_REGATTR(VLAN_FILTER), + .name = "vlan_filter", + .description = "Filter incoming packets for allowed VLANS", + .max = 1, + }, + { + .type = SWITCH_TYPE_INT, + .name = "cpuport", + .description = "CPU Port", + .set = rtl_set_cpuport, + .get = rtl_get_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + .type = SWITCH_TYPE_INT, + .name = "use_cpuport", + .description = "CPU Port handling flag", + .set = rtl_set_use_cpuport, + .get = rtl_get_use_cpuport, + .max = RTL8306_NUM_PORTS, + }, + { + RTL_GLOBAL_REGATTR(TRAP_CPU), + .name = "trap_cpu", + .description = "VLAN trap to CPU", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), + .name = "vlan_tag_aware", + .description = "Enable VLAN tag awareness", + .max = 1, + }, + { + RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), + .name = "tag_only", + .description = "Only accept tagged packets", + .max = 1, + }, +#endif +}; +static struct switch_attr rtl_port[] = { + { + RTL_PORT_REGATTR(PVID), + .name = "pvid", + .description = "Port VLAN ID", + .max = RTL8306_NUM_VLANS - 1, + }, +#ifdef DEBUG + { + RTL_PORT_REGATTR(NULL_VID_REPLACE), + .name = "null_vid", + .description = "NULL VID gets replaced by port default vid", + .max = 1, + }, + { + RTL_PORT_REGATTR(NON_PVID_DISCARD), + .name = "non_pvid_discard", + .description = "discard packets with VID != PVID", + .max = 1, + }, + { + RTL_PORT_REGATTR(VID_INSERT), + .name = "vid_insert_remove", + .description = "how should the switch insert and remove vids ?", + .max = 3, + }, + { + RTL_PORT_REGATTR(TAG_INSERT), + .name = "tag_insert", + .description = "tag insertion handling", + .max = 3, + }, +#endif +}; + +static struct switch_attr rtl_vlan[] = { + { + RTL_VLAN_REGATTR(VID), + .name = "vid", + .description = "VLAN ID (1-4095)", + .max = 4095, + }, +}; + +static const struct switch_dev_ops rtl8306_ops = { + .attr_global = { + .attr = rtl_globals, + .n_attr = ARRAY_SIZE(rtl_globals), + }, + .attr_port = { + .attr = rtl_port, + .n_attr = ARRAY_SIZE(rtl_port), + }, + .attr_vlan = { + .attr = rtl_vlan, + .n_attr = ARRAY_SIZE(rtl_vlan), + }, + + .get_vlan_ports = rtl_get_ports, + .set_vlan_ports = rtl_set_ports, + .apply_config = rtl_hw_apply, + .reset_switch = rtl_reset, + .get_port_link = rtl_get_port_link, +}; + +static int +rtl8306_config_init(struct phy_device *pdev) +{ + struct net_device *netdev = pdev->attached_dev; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + struct switch_val val; + unsigned int chipid, chipver, chiptype; + int err; + + /* Only init the switch for the primary PHY */ + if (pdev->mdio.addr != 0) + return 0; + + val.value.i = 1; + priv->dev.cpu_port = RTL8306_PORT_CPU; + priv->dev.ports = RTL8306_NUM_PORTS; + priv->dev.vlans = RTL8306_NUM_VLANS; + priv->dev.ops = &rtl8306_ops; + priv->do_cpu = 0; + priv->page = -1; + priv->bus = pdev->mdio.bus; + + chipid = rtl_get(dev, RTL_REG_CHIPID); + chipver = rtl_get(dev, RTL_REG_CHIPVER); + chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); + switch(chiptype) { + case 0: + case 2: + strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); + priv->type = RTL_TYPE_S; + break; + case 1: + strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SD; + break; + case 3: + strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); + priv->type = RTL_TYPE_SDM; + break; + default: + strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); + break; + } + + dev->name = priv->hwname; + rtl_hw_init(dev); + + printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); + + err = register_switch(dev, netdev); + if (err < 0) { + kfree(priv); + return err; + } + + return 0; +} + + +static int +rtl8306_fixup(struct phy_device *pdev) +{ + struct rtl_priv priv; + u16 chipid; + + /* Attach to primary LAN port and WAN port */ + if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4) + return 0; + + memset(&priv, 0, sizeof(priv)); + priv.fixup = true; + priv.page = -1; + priv.bus = pdev->mdio.bus; + chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); + if (chipid == 0x5988) + pdev->phy_id = RTL8306_MAGIC; + + return 0; +} + +static int +rtl8306_probe(struct phy_device *pdev) +{ + struct rtl_priv *priv; + + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->mdio.bus) + goto found; + } + priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->bus = pdev->mdio.bus; + +found: + pdev->priv = priv; + return 0; +} + +static void +rtl8306_remove(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + unregister_switch(&priv->dev); + kfree(priv); +} + +static int +rtl8306_config_aneg(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->mdio.addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + + return 0; +} + +static int +rtl8306_read_status(struct phy_device *pdev) +{ + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->mdio.addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } + + return 0; +} + + +static struct phy_driver rtl8306_driver = { + .name = "Realtek RTL8306S", +#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0)) + .flags = PHY_HAS_MAGICANEG, +#endif + .phy_id = RTL8306_MAGIC, + .phy_id_mask = 0xffffffff, + .features = PHY_BASIC_FEATURES, + .probe = &rtl8306_probe, + .remove = &rtl8306_remove, + .config_init = &rtl8306_config_init, + .config_aneg = &rtl8306_config_aneg, + .read_status = &rtl8306_read_status, +}; + + +static int __init +rtl_init(void) +{ + phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); + return phy_driver_register(&rtl8306_driver, THIS_MODULE); +} + +static void __exit +rtl_exit(void) +{ + phy_driver_unregister(&rtl8306_driver); +} + +module_init(rtl_init); +module_exit(rtl_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.c new file mode 100644 index 0000000000..c0cb680e23 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.c @@ -0,0 +1,1628 @@ +/* + * Realtek RTL8366 SMI interface driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +#include +#endif + +#include "rtl8366_smi.h" + +#define RTL8366_SMI_ACK_RETRY_COUNT 5 + +#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ +#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ + +static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) +{ + ndelay(smi->clk_delay); +} + +static void rtl8366_smi_start(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + /* + * Set GPIO pins to output mode, with initial state: + * SCK = 0, SDA = 1 + */ + gpio_direction_output(sck, 0); + gpio_direction_output(sda, 1); + rtl8366_smi_clk_delay(smi); + + /* CLK 1: 0 -> 1, 1 -> 0 */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + + /* CLK 2: */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); +} + +static void rtl8366_smi_stop(struct rtl8366_smi *smi) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 0); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sda, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* add a click */ + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 1); + + /* set GPIO pins to input mode */ + gpio_direction_input(sda); + gpio_direction_input(sck); +} + +static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + for (; len > 0; len--) { + rtl8366_smi_clk_delay(smi); + + /* prepare data */ + gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + gpio_set_value(sck, 0); + } +} + +static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) +{ + unsigned int sda = smi->gpio_sda; + unsigned int sck = smi->gpio_sck; + + gpio_direction_input(sda); + + for (*data = 0; len > 0; len--) { + u32 u; + + rtl8366_smi_clk_delay(smi); + + /* clocking */ + gpio_set_value(sck, 1); + rtl8366_smi_clk_delay(smi); + u = !!gpio_get_value(sda); + gpio_set_value(sck, 0); + + *data |= (u << (len - 1)); + } + + gpio_direction_output(sda, 0); +} + +static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) +{ + int retry_cnt; + + retry_cnt = 0; + do { + u32 ack; + + rtl8366_smi_read_bits(smi, 1, &ack); + if (ack == 0) + break; + + if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { + dev_err(smi->parent, "ACK timeout\n"); + return -ETIMEDOUT; + } + } while (1); + + return 0; +} + +static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return rtl8366_smi_wait_for_ack(smi); +} + +static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) +{ + rtl8366_smi_write_bits(smi, data, 8); + return 0; +} + +static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x00, 1); + + return 0; +} + +static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) +{ + u32 t; + + /* read data */ + rtl8366_smi_read_bits(smi, 8, &t); + *data = (t & 0xff); + + /* send an ACK */ + rtl8366_smi_write_bits(smi, 0x01, 1); + + return 0; +} + +static int __rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + unsigned long flags; + u8 lo = 0; + u8 hi = 0; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send READ command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_read); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* read DATA[7:0] */ + rtl8366_smi_read_byte0(smi, &lo); + /* read DATA[15:8] */ + rtl8366_smi_read_byte1(smi, &hi); + + *data = ((u32) lo) | (((u32) hi) << 8); + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} +/* Read/write via mdiobus */ +#define MDC_MDIO_CTRL0_REG 31 +#define MDC_MDIO_START_REG 29 +#define MDC_MDIO_CTRL1_REG 21 +#define MDC_MDIO_ADDRESS_REG 23 +#define MDC_MDIO_DATA_WRITE_REG 24 +#define MDC_MDIO_DATA_READ_REG 25 + +#define MDC_MDIO_START_OP 0xFFFF +#define MDC_MDIO_ADDR_OP 0x000E +#define MDC_MDIO_READ_OP 0x0001 +#define MDC_MDIO_WRITE_OP 0x0003 +#define MDC_REALTEK_PHY_ADDR 0x0 + +int __rtl8366_mdio_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write read control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_READ_OP); + + /* Write Start command to register 29 */ + mbus->write(smi->ext_mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Read data from register 25 */ + *data = mbus->read(mbus, phy_id, MDC_MDIO_DATA_READ_REG); + + mutex_unlock(&mbus->mdio_lock); + + return 0; +} + +static int __rtl8366_mdio_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + u32 phy_id = MDC_REALTEK_PHY_ADDR; + struct mii_bus *mbus = smi->ext_mbus; + + BUG_ON(in_interrupt()); + + mutex_lock(&mbus->mdio_lock); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address control code to register 31 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write address to register 23 */ + mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data to register 24 */ + mbus->write(mbus, phy_id, MDC_MDIO_DATA_WRITE_REG, data); + + /* Write Start command to register 29 */ + mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); + + /* Write data control code to register 21 */ + mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_WRITE_OP); + + mutex_unlock(&mbus->mdio_lock); + return 0; +} + +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_read_reg(smi, addr, data); + else + return __rtl8366_smi_read_reg(smi, addr, data); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); + +static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, + u32 addr, u32 data, bool ack) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&smi->lock, flags); + + rtl8366_smi_start(smi); + + /* send WRITE command */ + ret = rtl8366_smi_write_byte(smi, smi->cmd_write); + if (ret) + goto out; + + /* set ADDR[7:0] */ + ret = rtl8366_smi_write_byte(smi, addr & 0xff); + if (ret) + goto out; + + /* set ADDR[15:8] */ + ret = rtl8366_smi_write_byte(smi, addr >> 8); + if (ret) + goto out; + + /* write DATA[7:0] */ + ret = rtl8366_smi_write_byte(smi, data & 0xff); + if (ret) + goto out; + + /* write DATA[15:8] */ + if (ack) + ret = rtl8366_smi_write_byte(smi, data >> 8); + else + ret = rtl8366_smi_write_byte_noack(smi, data >> 8); + if (ret) + goto out; + + ret = 0; + + out: + rtl8366_smi_stop(smi); + spin_unlock_irqrestore(&smi->lock, flags); + + return ret; +} + +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + if (smi->ext_mbus) + return __rtl8366_mdio_write_reg(smi, addr, data); + else + return __rtl8366_smi_write_reg(smi, addr, data, true); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); + +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) +{ + return __rtl8366_smi_write_reg(smi, addr, data, false); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); + +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) +{ + u32 t; + int err; + + err = rtl8366_smi_read_reg(smi, addr, &t); + if (err) + return err; + + err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); + return err; + +} +EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); + +static int rtl8366_reset(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) { + smi->hw_reset(smi, true); + msleep(RTL8366_SMI_HW_STOP_DELAY); + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + return 0; + } + + return smi->ops->reset_chip(smi); +} + +static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) +{ + int err; + int i; + + *used = 0; + for (i = 0; i < smi->num_ports; i++) { + int index = 0; + + err = smi->ops->get_mc_index(smi, i, &index); + if (err) + return err; + + if (mc_index == index) { + *used = 1; + break; + } + } + + return 0; +} + +static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, + u32 untag, u32 fid) +{ + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Update the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlan4k.member = member; + vlan4k.untag = untag; + vlan4k.fid = fid; + err = smi->ops->set_vlan_4k(smi, &vlan4k); + if (err) + return err; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + struct rtl8366_vlan_mc vlanmc; + + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + /* update the MC entry */ + vlanmc.member = member; + vlanmc.untag = untag; + vlanmc.fid = fid; + + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + break; + } + } + + return err; +} + +static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int index; + + err = smi->ops->get_mc_index(smi, port, &index); + if (err) + return err; + + err = smi->ops->get_vlan_mc(smi, index, &vlanmc); + if (err) + return err; + + *val = vlanmc.vid; + return 0; +} + +static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, + unsigned vid) +{ + struct rtl8366_vlan_mc vlanmc; + struct rtl8366_vlan_4k vlan4k; + int err; + int i; + + /* Try to find an existing MC entry for this VID */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vid == vlanmc.vid) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* We have no MC entry for this VID, try to find an empty one */ + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->get_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + if (vlanmc.vid == 0 && vlanmc.member == 0) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + /* MC table is full, try to find an unused entry and replace it */ + for (i = 0; i < smi->num_vlan_mc; i++) { + int used; + + err = rtl8366_mc_is_used(smi, i, &used); + if (err) + return err; + + if (!used) { + /* Update the entry from the 4K table */ + err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); + if (err) + return err; + + vlanmc.vid = vid; + vlanmc.member = vlan4k.member; + vlanmc.untag = vlan4k.untag; + vlanmc.fid = vlan4k.fid; + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + + err = smi->ops->set_mc_index(smi, port, i); + return err; + } + } + + dev_err(smi->parent, + "all VLAN member configurations are in use\n"); + + return -ENOSPC; +} + +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + int err; + + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + + if (!enable) { + smi->vlan4k_enabled = 0; + err = smi->ops->enable_vlan4k(smi, enable); + } + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); + +static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + int err; + + if (enable) { + err = smi->ops->enable_vlan(smi, enable); + if (err) + return err; + + smi->vlan_enabled = enable; + } + + err = smi->ops->enable_vlan4k(smi, enable); + if (err) + return err; + + smi->vlan4k_enabled = enable; + return 0; +} + +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) +{ + int port; + int err; + + for (port = 0; port < smi->num_ports; port++) { + err = smi->ops->enable_port(smi, port, enable); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi) +{ + struct rtl8366_vlan_mc vlanmc; + int err; + int i; + + rtl8366_enable_vlan(smi, 0); + rtl8366_enable_vlan4k(smi, 0); + + /* clear VLAN member configurations */ + vlanmc.vid = 0; + vlanmc.priority = 0; + vlanmc.member = 0; + vlanmc.untag = 0; + vlanmc.fid = 0; + for (i = 0; i < smi->num_vlan_mc; i++) { + err = smi->ops->set_vlan_mc(smi, i, &vlanmc); + if (err) + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); + +static int rtl8366_init_vlan(struct rtl8366_smi *smi) +{ + int port; + int err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + for (port = 0; port < smi->num_ports; port++) { + u32 mask; + + if (port == smi->cpu_port) + mask = (1 << smi->num_ports) - 1; + else + mask = (1 << port) | (1 << smi->cpu_port); + + err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); + if (err) + return err; + + err = rtl8366_set_pvid(smi, port, (port + 1)); + if (err) + return err; + } + + return rtl8366_enable_vlan(smi, 1); +} + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); + +static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2s %6s %4s %6s %6s %3s\n", + "id", "vid","prio", "member", "untag", "fid"); + + for (i = 0; i < smi->num_vlan_mc; ++i) { + struct rtl8366_vlan_mc vlanmc; + + smi->ops->get_vlan_mc(smi, i, &vlanmc); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%2d %6d %4d 0x%04x 0x%04x %3d\n", + i, vlanmc.vid, vlanmc.priority, + vlanmc.member, vlanmc.untag, vlanmc.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +#define RTL8366_VLAN4K_PAGE_SIZE 64 +#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) + +static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + int i, len = 0; + int offset; + char *buf = smi->buf; + + if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "invalid page: %u\n", smi->dbg_vlan_4k_page); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4s %6s %6s %3s\n", + "vid", "member", "untag", "fid"); + + offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; + for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { + struct rtl8366_vlan_4k vlan4k; + + smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d 0x%04x 0x%04x %3d\n", + vlan4k.vid, vlan4k.member, + vlan4k.untag, vlan4k.fid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_pvid(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + char *buf = smi->buf; + int len = 0; + int i; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", + "port", "pvid"); + + for (i = 0; i < smi->num_ports; i++) { + int pvid; + int err; + + err = rtl8366_get_pvid(smi, i, &pvid); + if (err) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d error\n", i); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%4d %4d\n", i, pvid); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_read_debugfs_reg(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + u32 t, reg = smi->dbg_reg; + int err, len = 0; + char *buf = smi->buf; + + memset(buf, '\0', sizeof(smi->buf)); + + err = rtl8366_smi_read_reg(smi, reg, &t); + if (err) { + len += snprintf(buf, sizeof(smi->buf), + "Read failed (reg: 0x%04x)\n", reg); + return simple_read_from_buffer(user_buf, count, ppos, buf, len); + } + + len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", + reg, t); + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static ssize_t rtl8366_write_debugfs_reg(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; + unsigned long data; + u32 reg = smi->dbg_reg; + int err; + size_t len; + char *buf = smi->buf; + + len = min(count, sizeof(smi->buf) - 1); + if (copy_from_user(buf, user_buf, len)) { + dev_err(smi->parent, "copy from user failed\n"); + return -EFAULT; + } + + buf[len] = '\0'; + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + + if (kstrtoul(buf, 16, &data)) { + dev_err(smi->parent, "Invalid reg value %s\n", buf); + } else { + err = rtl8366_smi_write_reg(smi, reg, data); + if (err) { + dev_err(smi->parent, + "writing reg 0x%04x val 0x%04lx failed\n", + reg, data); + } + } + + return count; +} + +static ssize_t rtl8366_read_debugfs_mibs(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct rtl8366_smi *smi = file->private_data; + int i, j, len = 0; + char *buf = smi->buf; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", + "Counter"); + + for (i = 0; i < smi->num_ports; i++) { + char port_buf[10]; + + snprintf(port_buf, sizeof(port_buf), "Port %d", i); + len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", + port_buf); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + + for (i = 0; i < smi->num_mib_counters; i++) { + len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", + smi->mib_counters[i].name); + for (j = 0; j < smi->num_ports; j++) { + unsigned long long counter = 0; + + if (!smi->ops->get_mib_counter(smi, i, j, &counter)) + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12llu ", counter); + else + len += snprintf(buf + len, + sizeof(smi->buf) - len, + "%12s ", "error"); + } + len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations fops_rtl8366_regs = { + .read = rtl8366_read_debugfs_reg, + .write = rtl8366_write_debugfs_reg, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_mc = { + .read = rtl8366_read_debugfs_vlan_mc, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_vlan_4k = { + .read = rtl8366_read_debugfs_vlan_4k, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_pvid = { + .read = rtl8366_read_debugfs_pvid, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static const struct file_operations fops_rtl8366_mibs = { + .read = rtl8366_read_debugfs_mibs, + .open = rtl8366_debugfs_open, + .owner = THIS_MODULE +}; + +static void rtl8366_debugfs_init(struct rtl8366_smi *smi) +{ + struct dentry *node; + struct dentry *root; + + if (!smi->debugfs_root) + smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), + NULL); + + if (!smi->debugfs_root) { + dev_err(smi->parent, "Unable to create debugfs dir\n"); + return; + } + root = smi->debugfs_root; + + node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, + &smi->dbg_reg); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "reg"); + return; + } + + node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, + &fops_rtl8366_regs); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "val"); + return; + } + + node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, + &fops_rtl8366_vlan_mc); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_mc"); + return; + } + + node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, + &smi->dbg_vlan_4k_page); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k_page"); + return; + } + + node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, + &fops_rtl8366_vlan_4k); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "vlan_4k"); + return; + } + + node = debugfs_create_file("pvid", S_IRUSR, root, smi, + &fops_rtl8366_pvid); + if (!node) { + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "pvid"); + return; + } + + node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, + &fops_rtl8366_mibs); + if (!node) + dev_err(smi->parent, "Creating debugfs file '%s' failed\n", + "mibs"); +} + +static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) +{ + if (smi->debugfs_root) { + debugfs_remove_recursive(smi->debugfs_root); + smi->debugfs_root = NULL; + } +} +#else +static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} +static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} +#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ + +static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) +{ + int ret; + +#ifdef CONFIG_OF + struct device_node *np = NULL; + + np = of_get_child_by_name(smi->parent->of_node, "mdio-bus"); +#endif + + smi->mii_bus = mdiobus_alloc(); + if (smi->mii_bus == NULL) { + ret = -ENOMEM; + goto err; + } + + smi->mii_bus->priv = (void *) smi; + smi->mii_bus->name = dev_name(smi->parent); + smi->mii_bus->read = smi->ops->mii_read; + smi->mii_bus->write = smi->ops->mii_write; + snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", + dev_name(smi->parent)); + smi->mii_bus->parent = smi->parent; + smi->mii_bus->phy_mask = ~(0x1f); +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) + { + int i; + smi->mii_bus->irq = smi->mii_irq; + for (i = 0; i < PHY_MAX_ADDR; i++) + smi->mii_irq[i] = PHY_POLL; + } +#endif + +#ifdef CONFIG_OF + if (np) + ret = of_mdiobus_register(smi->mii_bus, np); + else +#endif + ret = mdiobus_register(smi->mii_bus); + + if (ret) + goto err_free; + + return 0; + + err_free: + mdiobus_free(smi->mii_bus); + err: + return ret; +} + +static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) +{ + mdiobus_unregister(smi->mii_bus); + mdiobus_free(smi->mii_bus); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + err = rtl8366_reset(smi); + if (err) + return err; + + err = smi->ops->setup(smi); + if (err) + return err; + + err = rtl8366_reset_vlan(smi); + if (err) + return err; + + err = rtl8366_enable_vlan(smi, 1); + if (err) + return err; + + return rtl8366_enable_all_ports(smi, 1); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); + +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_get_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); + +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366_set_pvid(smi, port, val); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); + +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int i, len = 0; + unsigned long long counter = 0; + char *buf = smi->buf; + + if (val->port_vlan >= smi->num_ports) + return -EINVAL; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "Port %d MIB counters\n", + val->port_vlan); + + for (i = 0; i < smi->num_mib_counters; ++i) { + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%-36s: ", smi->mib_counters[i].name); + if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, + &counter)) + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%llu\n", counter); + else + len += snprintf(buf + len, sizeof(smi->buf) - len, + "%s\n", "error"); + } + + val->value.s = buf; + val->len = len; + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); + +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + unsigned long long counter = 0; + int ret; + + if (port >= smi->num_ports) + return -EINVAL; + + ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter); + if (ret) + return ret; + + stats->tx_bytes = counter; + + ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter); + if (ret) + return ret; + + stats->rx_bytes = counter; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats); + +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + int i; + u32 len = 0; + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char *buf = smi->buf; + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + memset(buf, '\0', sizeof(smi->buf)); + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "VLAN %d: Ports: '", vlan4k.vid); + + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & (1 << i))) + continue; + + len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, + (vlan4k.untag & (1 << i)) ? "" : "t"); + } + + len += snprintf(buf + len, sizeof(smi->buf) - len, + "', members=%04x, untag=%04x, fid=%u", + vlan4k.member, vlan4k.untag, vlan4k.fid); + + val->value.s = buf; + val->len = len; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); + +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + struct rtl8366_vlan_4k vlan4k; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + + port = &val->value.ports[0]; + val->len = 0; + for (i = 0; i < smi->num_ports; i++) { + if (!(vlan4k.member & BIT(i))) + continue; + + port->id = i; + port->flags = (vlan4k.untag & BIT(i)) ? + 0 : BIT(SWITCH_PORT_FLAG_TAGGED); + val->len++; + port++; + } + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); + +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + struct switch_port *port; + u32 member = 0; + u32 untag = 0; + int err; + int i; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + port = &val->value.ports[0]; + for (i = 0; i < val->len; i++, port++) { + int pvid = 0; + member |= BIT(port->id); + + if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) + untag |= BIT(port->id); + + /* + * To ensure that we have a valid MC entry for this VLAN, + * initialize the port VLAN ID here. + */ + err = rtl8366_get_pvid(smi, port->id, &pvid); + if (err < 0) + return err; + if (pvid == 0) { + err = rtl8366_set_pvid(smi, port->id, val->port_vlan); + if (err < 0) + return err; + } + } + + return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); + +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + val->value.i = vlan4k.fid; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); + +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_vlan_4k vlan4k; + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) + return -EINVAL; + + if (val->value.i < 0 || val->value.i > attr->max) + return -EINVAL; + + err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); + if (err) + return err; + + return rtl8366_set_vlan(smi, val->port_vlan, + vlan4k.member, + vlan4k.untag, + val->value.i); +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); + +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + val->value.i = smi->vlan_enabled; + else + val->value.i = smi->vlan4k_enabled; + + return 0; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); + +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + + if (attr->ofs > 2) + return -EINVAL; + + if (attr->ofs == 1) + err = rtl8366_enable_vlan(smi, val->value.i); + else + err = rtl8366_enable_vlan4k(smi, val->value.i); + + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) +{ + struct rtl8366_smi *smi; + + BUG_ON(!parent); + + smi = kzalloc(sizeof(*smi), GFP_KERNEL); + if (!smi) { + dev_err(parent, "no memory for private data\n"); + return NULL; + } + + smi->parent = parent; + return smi; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); + +static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) +{ + int err; + + if (!smi->ext_mbus) { + err = gpio_request(smi->gpio_sda, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sda, err); + goto err_out; + } + + err = gpio_request(smi->gpio_sck, name); + if (err) { + printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", + smi->gpio_sck, err); + goto err_free_sda; + } + } + + spin_lock_init(&smi->lock); + + /* start the switch */ + if (smi->hw_reset) { + smi->hw_reset(smi, false); + msleep(RTL8366_SMI_HW_START_DELAY); + } + + return 0; + + err_free_sda: + gpio_free(smi->gpio_sda); + err_out: + return err; +} + +static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + if (smi->hw_reset) + smi->hw_reset(smi, true); + + if (!smi->ext_mbus) { + gpio_free(smi->gpio_sck); + gpio_free(smi->gpio_sda); + } +} + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) +{ + static struct rtl8366_smi smi; + enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; + u32 reg = 0; + + memset(&smi, 0, sizeof(smi)); + smi.gpio_sda = pdata->gpio_sda; + smi.gpio_sck = pdata->gpio_sck; + smi.clk_delay = 10; + smi.cmd_read = 0xa9; + smi.cmd_write = 0xa8; + + if (__rtl8366_smi_init(&smi, "rtl8366")) + goto out; + + if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) + goto cleanup; + + switch(reg) { + case 0x6027: + printk("Found an RTL8366S switch\n"); + type = RTL8366_TYPE_S; + break; + case 0x5937: + printk("Found an RTL8366RB switch\n"); + type = RTL8366_TYPE_RB; + break; + default: + printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); + break; + } + +cleanup: + __rtl8366_smi_cleanup(&smi); +out: + return type; +} + +int rtl8366_smi_init(struct rtl8366_smi *smi) +{ + int err; + + if (!smi->ops) + return -EINVAL; + + err = __rtl8366_smi_init(smi, dev_name(smi->parent)); + if (err) + goto err_out; + + if (!smi->ext_mbus) + dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", + smi->gpio_sda, smi->gpio_sck); + else + dev_info(smi->parent, "using MDIO bus '%s'\n", smi->ext_mbus->name); + + err = smi->ops->detect(smi); + if (err) { + dev_err(smi->parent, "chip detection failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_reset(smi); + if (err) + goto err_free_sck; + + err = smi->ops->setup(smi); + if (err) { + dev_err(smi->parent, "chip setup failed, err=%d\n", err); + goto err_free_sck; + } + + err = rtl8366_init_vlan(smi); + if (err) { + dev_err(smi->parent, "VLAN initialization failed, err=%d\n", + err); + goto err_free_sck; + } + + err = rtl8366_enable_all_ports(smi, 1); + if (err) + goto err_free_sck; + + err = rtl8366_smi_mii_init(smi); + if (err) + goto err_free_sck; + + rtl8366_debugfs_init(smi); + + return 0; + + err_free_sck: + __rtl8366_smi_cleanup(smi); + err_out: + return err; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_init); + +void rtl8366_smi_cleanup(struct rtl8366_smi *smi) +{ + rtl8366_debugfs_remove(smi); + rtl8366_smi_mii_cleanup(smi); + __rtl8366_smi_cleanup(smi); +} +EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); + +#ifdef CONFIG_OF +static void rtl8366_smi_reset(struct rtl8366_smi *smi, bool active) +{ + if (active) + reset_control_assert(smi->reset); + else + reset_control_deassert(smi->reset); +} + +int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0); + int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0); + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio_node; + + mdio_node = of_parse_phandle(np, "mii-bus", 0); + if (!mdio_node) { + dev_err(&pdev->dev, "cannot find mdio node phandle"); + goto try_gpio; + } + + smi->ext_mbus = of_mdio_find_bus(mdio_node); + if (!smi->ext_mbus) { + dev_err(&pdev->dev, + "cannot find mdio bus from bus handle"); + goto try_gpio; + } + + return 0; + +try_gpio: + if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) { + dev_err(&pdev->dev, "gpios missing in devictree\n"); + return -EINVAL; + } + + smi->gpio_sda = sda; + smi->gpio_sck = sck; + smi->reset = devm_reset_control_get(&pdev->dev, "switch"); + if (!IS_ERR(smi->reset)) + smi->hw_reset = rtl8366_smi_reset; + + return 0; +} +#else +static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + return -ENODEV; +} +#endif + +int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata = pdev->dev.platform_data; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + smi->gpio_sda = pdata->gpio_sda; + smi->gpio_sck = pdata->gpio_sck; + smi->hw_reset = pdata->hw_reset; + + return 0; +} + + +struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_alloc(&pdev->dev); + if (!smi) + return NULL; + + if (pdev->dev.of_node) + err = rtl8366_smi_probe_of(pdev, smi); + else + err = rtl8366_smi_probe_plat(pdev, smi); + + if (err) + goto free_smi; + + return smi; + +free_smi: + kfree(smi); + return NULL; +} +EXPORT_SYMBOL_GPL(rtl8366_smi_probe); + +MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.h b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.h new file mode 100644 index 0000000000..d1d988a372 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366_smi.h @@ -0,0 +1,160 @@ +/* + * Realtek RTL8366 SMI interface driver defines + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_SMI_H +#define _RTL8366_SMI_H + +#include +#include +#include +#include + +struct rtl8366_smi_ops; +struct rtl8366_vlan_ops; +struct mii_bus; +struct dentry; +struct inode; +struct file; + +struct rtl8366_mib_counter { + unsigned base; + unsigned offset; + unsigned length; + const char *name; +}; + +struct rtl8366_smi { + struct device *parent; + unsigned int gpio_sda; + unsigned int gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + unsigned int clk_delay; /* ns */ + u8 cmd_read; + u8 cmd_write; + spinlock_t lock; + struct mii_bus *mii_bus; + int mii_irq[PHY_MAX_ADDR]; + struct switch_dev sw_dev; + + unsigned int cpu_port; + unsigned int num_ports; + unsigned int num_vlan_mc; + unsigned int num_mib_counters; + struct rtl8366_mib_counter *mib_counters; + + struct rtl8366_smi_ops *ops; + + int vlan_enabled; + int vlan4k_enabled; + + char buf[4096]; + + struct reset_control *reset; + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS + struct dentry *debugfs_root; + u16 dbg_reg; + u8 dbg_vlan_4k_page; +#endif + struct mii_bus *ext_mbus; +}; + +struct rtl8366_vlan_mc { + u16 vid; + u16 untag; + u16 member; + u8 fid; + u8 priority; +}; + +struct rtl8366_vlan_4k { + u16 vid; + u16 untag; + u16 member; + u8 fid; +}; + +struct rtl8366_smi_ops { + int (*detect)(struct rtl8366_smi *smi); + int (*reset_chip)(struct rtl8366_smi *smi); + int (*setup)(struct rtl8366_smi *smi); + + int (*mii_read)(struct mii_bus *bus, int addr, int reg); + int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); + + int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc); + int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc); + int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k); + int (*set_vlan_4k)(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k); + int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); + int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); + int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val); + int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); + int (*enable_vlan)(struct rtl8366_smi *smi, int enable); + int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); + int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); +}; + +struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); +int rtl8366_smi_init(struct rtl8366_smi *smi); +void rtl8366_smi_cleanup(struct rtl8366_smi *smi); +int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); +int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); +int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); + +int rtl8366_reset_vlan(struct rtl8366_smi *smi); +int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); +int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); + +#ifdef CONFIG_RTL8366_SMI_DEBUG_FS +int rtl8366_debugfs_open(struct inode *inode, struct file *file); +#endif + +static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) +{ + return container_of(sw, struct rtl8366_smi, sw_dev); +} + +int rtl8366_sw_reset_switch(struct switch_dev *dev); +int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); +int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); +int rtl8366_sw_get_port_mib(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_info(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); +int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val); +int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats, + int txb_id, int rxb_id); + +struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev); + +#endif /* _RTL8366_SMI_H */ diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8366rb.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366rb.c new file mode 100644 index 0000000000..dc394c02b6 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366rb.c @@ -0,0 +1,1532 @@ +/* + * Platform driver for the Realtek RTL8366RB ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * Copyright (C) 2010 Roman Yeryomin + * Copyright (C) 2011 Colin Leitner + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" +#define RTL8366RB_DRIVER_VER "0.2.4" + +#define RTL8366RB_PHY_NO_MAX 4 +#define RTL8366RB_PHY_PAGE_MAX 7 +#define RTL8366RB_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366RB_SGCR 0x0000 +#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) +#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) +#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) +#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) +#define RTL8366RB_SGCR_EN_VLAN BIT(13) +#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) + +/* Port Enable Control register */ +#define RTL8366RB_PECR 0x0001 + +/* Port Mirror Control Register */ +#define RTL8366RB_PMCR 0x0007 +#define RTL8366RB_PMCR_SOURCE_PORT(_x) (_x) +#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f +#define RTL8366RB_PMCR_MONITOR_PORT(_x) ((_x) << 4) +#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 +#define RTL8366RB_PMCR_MIRROR_RX BIT(8) +#define RTL8366RB_PMCR_MIRROR_TX BIT(9) +#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) +#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) + +/* Switch Security Control registers */ +#define RTL8366RB_SSCR0 0x0002 +#define RTL8366RB_SSCR1 0x0003 +#define RTL8366RB_SSCR2 0x0004 +#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366RB_RESET_CTRL_REG 0x0100 +#define RTL8366RB_CHIP_CTRL_RESET_HW 1 +#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A +#define RTL8366RB_CHIP_VERSION_MASK 0xf +#define RTL8366RB_CHIP_ID_REG 0x0509 +#define RTL8366RB_CHIP_ID_8366 0x5937 + +/* PHY registers control */ +#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 +#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 + +#define RTL8366RB_PHY_CTRL_READ 1 +#define RTL8366RB_PHY_CTRL_WRITE 0 + +#define RTL8366RB_PHY_REG_MASK 0x1f +#define RTL8366RB_PHY_PAGE_OFFSET 5 +#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) +#define RTL8366RB_PHY_NO_OFFSET 9 +#define RTL8366RB_PHY_NO_MASK (0x1f << 9) + +#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f + +/* LED control registers */ +#define RTL8366RB_LED_BLINKRATE_REG 0x0430 +#define RTL8366RB_LED_BLINKRATE_BIT 0 +#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366RB_LED_CTRL_REG 0x0431 +#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 +#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 + +#define RTL8366RB_MIB_COUNT 33 +#define RTL8366RB_GLOBAL_MIB_COUNT 1 +#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 +#define RTL8366RB_MIB_COUNTER_BASE 0x1000 +#define RTL8366RB_MIB_CTRL_REG 0x13F0 +#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC +#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) +#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) +#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) + +#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 +#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C +#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 + + +#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) + + +#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 +#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366RB_PORT_NUM_CPU 5 +#define RTL8366RB_NUM_PORTS 6 +#define RTL8366RB_NUM_VLANS 16 +#define RTL8366RB_NUM_LEDGROUPS 4 +#define RTL8366RB_NUM_VIDS 4096 +#define RTL8366RB_PRIORITYMAX 7 +#define RTL8366RB_FIDMAX 7 + + +#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ +#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ + +#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5 | \ + RTL8366RB_PORT_CPU) + +#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4 | \ + RTL8366RB_PORT_5) + +#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ + RTL8366RB_PORT_2 | \ + RTL8366RB_PORT_3 | \ + RTL8366RB_PORT_4) + +#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU + +#define RTL8366RB_VLAN_VID_MASK 0xfff +#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 +#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 +#define RTL8366RB_VLAN_UNTAG_SHIFT 8 +#define RTL8366RB_VLAN_UNTAG_MASK 0xff +#define RTL8366RB_VLAN_MEMBER_MASK 0xff +#define RTL8366RB_VLAN_FID_MASK 0x7 + + +/* Port ingress bandwidth control */ +#define RTL8366RB_IB_BASE 0x0200 +#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) +#define RTL8366RB_IB_BDTH_MASK 0x3fff +#define RTL8366RB_IB_PREIFG_OFFSET 14 +#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) + +/* Port egress bandwidth control */ +#define RTL8366RB_EB_BASE 0x02d1 +#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) +#define RTL8366RB_EB_BDTH_MASK 0x3fff +#define RTL8366RB_EB_PREIFG_REG 0x02f8 +#define RTL8366RB_EB_PREIFG_OFFSET 9 +#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) + +#define RTL8366RB_BDTH_SW_MAX 1048512 +#define RTL8366RB_BDTH_UNIT 64 +#define RTL8366RB_BDTH_REG_DEFAULT 16383 + +/* QOS */ +#define RTL8366RB_QOS_BIT 15 +#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) +/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ +#define RTL8366RB_QOS_DEFAULT_PREIFG 1 + + +#define RTL8366RB_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366RB_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, + RTL8366RB_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366rb_setup(struct rtl8366_smi *smi) +{ + int err; +#ifdef CONFIG_OF + unsigned i; + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; + + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, + RTL8366RB_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366RB_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366RB_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366RB_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, + RTL8366RB_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | + ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | + (addr & RTL8366RB_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) + return -EINVAL; + + addr = RTL8366RB_MIB_COUNTER_BASE + + RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + + rtl8366rb_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366RB_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366RB_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, + vid & RTL8366RB_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[3]; + int err; + int i; + + if (vlan4k->vid >= RTL8366RB_NUM_VIDS || + vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, + RTL8366RB_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & + RTL8366RB_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & + RTL8366RB_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; + vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[3]; + int err; + int i; + + if (index >= RTL8366RB_NUM_VLANS || + vlanmc->vid >= RTL8366RB_NUM_VIDS || + vlanmc->priority > RTL8366RB_PRIORITYMAX || + vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366RB_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << + RTL8366RB_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << + RTL8366RB_VLAN_UNTAG_SHIFT); + data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; + + for (i = 0; i < 3; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366RB_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366RB_PORT_VLAN_CTRL_MASK; + + return 0; + +} + +static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), + RTL8366RB_PORT_VLAN_CTRL_MASK << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << + RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366RB_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366RB_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, + (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); +} + +static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, + RTL8366RB_SGCR_EN_VLAN_4KTB, + (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); +} + +static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_GLOBAL_RESET); +} + +static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, + RTL8366RB_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366RB_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366RB_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366RB_SSCR1, portmask); + + return 0; +} + +static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { + reg = RTL8366RB_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366RB_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 mask, data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + mask = 1 << val->port_vlan ; + if (val->value.i) + data = mask; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); +} + +static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); + if (data & (1 << val->port_vlan)) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), + RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, + val->value.i | + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); + +} + +static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); + data &= RTL8366RB_IB_BDTH_MASK; + if (data < RTL8366RB_IB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, + RTL8366RB_EB_PREIFG_MASK, + (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); + + if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) + val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; + else + val->value.i = RTL8366RB_BDTH_REG_DEFAULT; + + return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), + RTL8366RB_EB_BDTH_MASK, val->value.i ); + +} + +static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); + data &= RTL8366RB_EB_BDTH_MASK; + if (data < RTL8366RB_EB_BDTH_MASK) + data += 1; + + val->value.i = (int)data * RTL8366RB_BDTH_UNIT; + + return 0; +} + +static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_QOS_MASK; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); +} + +static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); + if (data & RTL8366RB_QOS_MASK) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_RX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data); +} + +static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_RX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_TX; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data); +} + +static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_TX) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_ISO; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data); +} + +static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_ISO) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + if (val->value.i) + data = RTL8366RB_PMCR_MIRROR_SPC; + else + data = 0; + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data); +} + +static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + if (data & RTL8366RB_PMCR_MIRROR_SPC) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4; + + return 0; +} + +static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i); + + return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data); +} + +static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); + val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK; + + return 0; +} + +static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366RB_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, + RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); +} + +static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366rb_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366rb_sw_set_learning_enable, + .get = rtl8366rb_sw_get_learning_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366rb_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366rb_sw_set_blinkrate, + .get = rtl8366rb_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_qos", + .description = "Enable QOS", + .set = rtl8366rb_sw_set_qos_enable, + .get = rtl8366rb_sw_get_qos_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_rx", + .description = "Enable mirroring of RX packets", + .set = rtl8366rb_sw_set_mirror_rx_enable, + .get = rtl8366rb_sw_get_mirror_rx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_tx", + .description = "Enable mirroring of TX packets", + .set = rtl8366rb_sw_set_mirror_tx_enable, + .get = rtl8366rb_sw_get_mirror_tx_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_monitor_isolation", + .description = "Enable isolation of monitor port (TX packets will be dropped)", + .set = rtl8366rb_sw_set_monitor_isolation_enable, + .get = rtl8366rb_sw_get_monitor_isolation_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_mirror_pause_frames", + .description = "Enable mirroring of RX pause frames", + .set = rtl8366rb_sw_set_mirror_pause_frames_enable, + .get = rtl8366rb_sw_get_mirror_pause_frames_enable, + .max = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_monitor_port", + .description = "Mirror monitor port", + .set = rtl8366rb_sw_set_mirror_monitor_port, + .get = rtl8366rb_sw_get_mirror_monitor_port, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "mirror_source_port", + .description = "Mirror source port", + .set = rtl8366rb_sw_set_mirror_source_port, + .get = rtl8366rb_sw_get_mirror_source_port, + .max = 5 + }, +}; + +static struct switch_attr rtl8366rb_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366rb_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366rb_sw_set_port_led, + .get = rtl8366rb_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "disable", + .description = "Get/Set port state (enabled or disabled)", + .max = 1, + .set = rtl8366rb_sw_set_port_disable, + .get = rtl8366rb_sw_get_port_disable, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_in", + .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_in, + .get = rtl8366rb_sw_get_port_rate_in, + }, { + .type = SWITCH_TYPE_INT, + .name = "rate_out", + .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", + .max = RTL8366RB_BDTH_SW_MAX, + .set = rtl8366rb_sw_set_port_rate_out, + .get = rtl8366rb_sw_get_port_rate_out, + }, +}; + +static struct switch_attr rtl8366rb_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366RB_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366rb_globals, + .n_attr = ARRAY_SIZE(rtl8366rb_globals), + }, + .attr_port = { + .attr = rtl8366rb_port, + .n_attr = ARRAY_SIZE(rtl8366rb_port), + }, + .attr_vlan = { + .attr = rtl8366rb_vlan, + .n_attr = ARRAY_SIZE(rtl8366rb_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366rb_sw_get_port_link, + .get_port_stats = rtl8366rb_sw_get_port_stats, +}; + +static int rtl8366rb_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366RB"; + dev->cpu_port = RTL8366RB_PORT_NUM_CPU; + dev->ports = RTL8366RB_NUM_PORTS; + dev->vlans = RTL8366RB_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366rb_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366RB_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366rb_smi_ops = { + .detect = rtl8366rb_detect, + .reset_chip = rtl8366rb_reset_chip, + .setup = rtl8366rb_setup, + + .mii_read = rtl8366rb_mii_read, + .mii_write = rtl8366rb_mii_write, + + .get_vlan_mc = rtl8366rb_get_vlan_mc, + .set_vlan_mc = rtl8366rb_set_vlan_mc, + .get_vlan_4k = rtl8366rb_get_vlan_4k, + .set_vlan_4k = rtl8366rb_set_vlan_4k, + .get_mc_index = rtl8366rb_get_mc_index, + .set_mc_index = rtl8366rb_set_mc_index, + .get_mib_counter = rtl8366rb_get_mib_counter, + .is_vlan_valid = rtl8366rb_is_vlan_valid, + .enable_vlan = rtl8366rb_enable_vlan, + .enable_vlan4k = rtl8366rb_enable_vlan4k, + .enable_port = rtl8366rb_enable_port, +}; + +static int rtl8366rb_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366RB_DRIVER_DESC + " version " RTL8366RB_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366rb_smi_ops; + smi->cpu_port = RTL8366RB_PORT_NUM_CPU; + smi->num_ports = RTL8366RB_NUM_PORTS; + smi->num_vlan_mc = RTL8366RB_NUM_VLANS; + smi->mib_counters = rtl8366rb_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366rb_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366rb_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366rb_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366rb_match[] = { + { .compatible = "realtek,rtl8366rb" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366rb_match); +#endif + +static struct platform_driver rtl8366rb_driver = { + .driver = { + .name = RTL8366RB_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rtl8366rb_match), + }, + .probe = rtl8366rb_probe, + .remove = rtl8366rb_remove, +}; + +static int __init rtl8366rb_module_init(void) +{ + return platform_driver_register(&rtl8366rb_driver); +} +module_init(rtl8366rb_module_init); + +static void __exit rtl8366rb_module_exit(void) +{ + platform_driver_unregister(&rtl8366rb_driver); +} +module_exit(rtl8366rb_module_exit); + +MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); +MODULE_VERSION(RTL8366RB_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_AUTHOR("Roman Yeryomin "); +MODULE_AUTHOR("Colin Leitner "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8366s.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366s.c new file mode 100644 index 0000000000..3f458f9dec --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8366s.c @@ -0,0 +1,1320 @@ +/* + * Platform driver for the Realtek RTL8366S ethernet switch + * + * Copyright (C) 2009-2010 Gabor Juhos + * Copyright (C) 2010 Antti Seppälä + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" +#define RTL8366S_DRIVER_VER "0.2.2" + +#define RTL8366S_PHY_NO_MAX 4 +#define RTL8366S_PHY_PAGE_MAX 7 +#define RTL8366S_PHY_ADDR_MAX 31 + +/* Switch Global Configuration register */ +#define RTL8366S_SGCR 0x0000 +#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) +#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) +#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) +#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) +#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) +#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) +#define RTL8366S_SGCR_EN_VLAN BIT(13) + +/* Port Enable Control register */ +#define RTL8366S_PECR 0x0001 + +/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */ +#define RTL8366S_GREEN_ETHERNET_CTRL_REG 0x000a +#define RTL8366S_GREEN_ETHERNET_CTRL_MASK 0x0018 +#define RTL8366S_GREEN_ETHERNET_TX_BIT (1 << 3) +#define RTL8366S_GREEN_ETHERNET_RX_BIT (1 << 4) + +/* Switch Security Control registers */ +#define RTL8366S_SSCR0 0x0002 +#define RTL8366S_SSCR1 0x0003 +#define RTL8366S_SSCR2 0x0004 +#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) + +#define RTL8366S_RESET_CTRL_REG 0x0100 +#define RTL8366S_CHIP_CTRL_RESET_HW 1 +#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) + +#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 +#define RTL8366S_CHIP_VERSION_MASK 0xf +#define RTL8366S_CHIP_ID_REG 0x0105 +#define RTL8366S_CHIP_ID_8366 0x8366 + +/* PHY registers control */ +#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 +#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 + +#define RTL8366S_PHY_CTRL_READ 1 +#define RTL8366S_PHY_CTRL_WRITE 0 + +#define RTL8366S_PHY_REG_MASK 0x1f +#define RTL8366S_PHY_PAGE_OFFSET 5 +#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) +#define RTL8366S_PHY_NO_OFFSET 9 +#define RTL8366S_PHY_NO_MASK (0x1f << 9) + +/* Green Ethernet Feature for PHY ports */ +#define RTL8366S_PHY_POWER_SAVING_CTRL_REG 12 +#define RTL8366S_PHY_POWER_SAVING_MASK 0x1000 + +/* LED control registers */ +#define RTL8366S_LED_BLINKRATE_REG 0x0420 +#define RTL8366S_LED_BLINKRATE_BIT 0 +#define RTL8366S_LED_BLINKRATE_MASK 0x0007 + +#define RTL8366S_LED_CTRL_REG 0x0421 +#define RTL8366S_LED_0_1_CTRL_REG 0x0422 +#define RTL8366S_LED_2_3_CTRL_REG 0x0423 + +#define RTL8366S_MIB_COUNT 33 +#define RTL8366S_GLOBAL_MIB_COUNT 1 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 +#define RTL8366S_MIB_COUNTER_BASE 0x1000 +#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 +#define RTL8366S_MIB_COUNTER_BASE2 0x1180 +#define RTL8366S_MIB_CTRL_REG 0x11F0 +#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF +#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 +#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 + +#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 +#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 +#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC + + +#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 +#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ + (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) +#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf +#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) + + +#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B +#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 + +#define RTL8366S_VLAN_TB_CTRL_REG 0x010F + +#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 +#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 +#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 + +#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) + +#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 + +#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 +#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 +#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 +#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 +#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 +#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 + + +#define RTL8366S_PORT_NUM_CPU 5 +#define RTL8366S_NUM_PORTS 6 +#define RTL8366S_NUM_VLANS 16 +#define RTL8366S_NUM_LEDGROUPS 4 +#define RTL8366S_NUM_VIDS 4096 +#define RTL8366S_PRIORITYMAX 7 +#define RTL8366S_FIDMAX 7 + + +#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ +#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ +#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ +#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ + +#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ +#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ + +#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4 | \ + RTL8366S_PORT_UNKNOWN) + +#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ + RTL8366S_PORT_2 | \ + RTL8366S_PORT_3 | \ + RTL8366S_PORT_4) + +#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ + RTL8366S_PORT_CPU) + +#define RTL8366S_VLAN_VID_MASK 0xfff +#define RTL8366S_VLAN_PRIORITY_SHIFT 12 +#define RTL8366S_VLAN_PRIORITY_MASK 0x7 +#define RTL8366S_VLAN_MEMBER_MASK 0x3f +#define RTL8366S_VLAN_UNTAG_SHIFT 6 +#define RTL8366S_VLAN_UNTAG_MASK 0x3f +#define RTL8366S_VLAN_FID_SHIFT 12 +#define RTL8366S_VLAN_FID_MASK 0x7 + +#define RTL8366S_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8366S_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 4, "EtherStatsOctets" }, + { 0, 8, 2, "EtherStatsUnderSizePkts" }, + { 0, 10, 2, "EtherFragments" }, + { 0, 12, 2, "EtherStatsPkts64Octets" }, + { 0, 14, 2, "EtherStatsPkts65to127Octets" }, + { 0, 16, 2, "EtherStatsPkts128to255Octets" }, + { 0, 18, 2, "EtherStatsPkts256to511Octets" }, + { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 24, 2, "EtherOversizeStats" }, + { 0, 26, 2, "EtherStatsJabbers" }, + { 0, 28, 2, "IfInUcastPkts" }, + { 0, 30, 2, "EtherStatsMulticastPkts" }, + { 0, 32, 2, "EtherStatsBroadcastPkts" }, + { 0, 34, 2, "EtherStatsDropEvents" }, + { 0, 36, 2, "Dot3StatsFCSErrors" }, + { 0, 38, 2, "Dot3StatsSymbolErrors" }, + { 0, 40, 2, "Dot3InPauseFrames" }, + { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 44, 4, "IfOutOctets" }, + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + + /* + * The following counters are accessible at a different + * base address. + */ + { 1, 0, 2, "Dot1dTpPortInDiscards" }, + { 1, 2, 2, "IfOutUcastPkts" }, + { 1, 4, 2, "IfOutMulticastPkts" }, + { 1, 6, 2, "IfOutBroadcastPkts" }, +}; + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static int rtl8366s_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + u32 data; + + rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, + RTL8366S_CHIP_CTRL_RESET_HW); + do { + msleep(1); + if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) + return -EIO; + + if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) + break; + } while (--timeout); + + if (!timeout) { + printk("Timeout waiting for the switch to reset\n"); + return -EIO; + } + + return 0; +} + +static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 *data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_READ); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, 0); + if (ret) + return ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_no, u32 page, u32 addr, u32 data) +{ + u32 reg; + int ret; + + if (phy_no > RTL8366S_PHY_NO_MAX) + return -EINVAL; + + if (page > RTL8366S_PHY_PAGE_MAX) + return -EINVAL; + + if (addr > RTL8366S_PHY_ADDR_MAX) + return -EINVAL; + + ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, + RTL8366S_PHY_CTRL_WRITE); + if (ret) + return ret; + + reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | + ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | + (addr & RTL8366S_PHY_REG_MASK); + + ret = rtl8366_smi_write_reg(smi, reg, data); + if (ret) + return ret; + + return 0; +} + +static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + u32 phyData; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + if (enable) + phyData |= RTL8366S_PHY_POWER_SAVING_MASK; + else + phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK; + + err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData); + if (err) + return err; + + return 0; +} + +static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable) +{ + int err; + unsigned i; + u32 data = 0; + + if (!enable) { + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + rtl8366s_set_green_port(smi, i, 0); + } + } + + if (enable) + data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT); + + REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data); + + return 0; +} + +static int rtl8366s_setup(struct rtl8366_smi *smi) +{ + struct rtl8366_platform_data *pdata; + int err; + unsigned i; +#ifdef CONFIG_OF + struct device_node *np; + unsigned num_initvals; + const __be32 *paddr; +#endif + + pdata = smi->parent->platform_data; + if (pdata && pdata->num_initvals && pdata->initvals) { + dev_info(smi->parent, "applying initvals\n"); + for (i = 0; i < pdata->num_initvals; i++) + REG_WR(smi, pdata->initvals[i].reg, + pdata->initvals[i].val); + } + +#ifdef CONFIG_OF + np = smi->parent->of_node; + + paddr = of_get_property(np, "realtek,initvals", &num_initvals); + if (paddr) { + dev_info(smi->parent, "applying initvals from DTS\n"); + + if (num_initvals < (2 * sizeof(*paddr))) + return -EINVAL; + + num_initvals /= sizeof(*paddr); + + for (i = 0; i < num_initvals - 1; i += 2) { + u32 reg = be32_to_cpup(paddr + i); + u32 val = be32_to_cpup(paddr + i + 1); + + REG_WR(smi, reg, val); + } + } + + if (of_property_read_bool(np, "realtek,green-ethernet-features")) { + dev_info(smi->parent, "activating Green Ethernet features\n"); + + err = rtl8366s_set_green(smi, 1); + if (err) + return err; + + for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { + err = rtl8366s_set_green_port(smi, i, 1); + if (err) + return err; + } + } +#endif + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, + RTL8366S_SGCR_MAX_LENGTH_1536); + + /* enable learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, 0); + + /* enable auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, 0); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); + + /* don't drop packets whose DA has not been learned */ + REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); + + return 0; +} + +static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) + return -EINVAL; + + switch (rtl8366s_mib_counters[counter].base) { + case 0: + addr = RTL8366S_MIB_COUNTER_BASE + + RTL8366S_MIB_COUNTER_PORT_OFFSET * port; + break; + + case 1: + addr = RTL8366S_MIB_COUNTER_BASE2 + + RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; + break; + + default: + return -EINVAL; + } + + addr += rtl8366s_mib_counters[counter].offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + data = 0; /* writing data will be discard by ASIC */ + err = rtl8366_smi_write_reg(smi, addr, data); + if (err) + return err; + + /* read MIB control register */ + err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); + if (err) + return err; + + if (data & RTL8366S_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8366S_MIB_CTRL_RESET_MASK) + return -EIO; + + mibvalue = 0; + for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { + err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); + if (err) + return err; + + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8366S_NUM_VIDS) + return -EINVAL; + + /* write VID */ + err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, + vid & RTL8366S_VLAN_VID_MASK); + if (err) + return err; + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_READ_CTRL); + if (err) + return err; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_TABLE_READ_BASE + i, + &data[i]); + if (err) + return err; + } + + vlan4k->vid = vid; + vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[2]; + int err; + int i; + + if (vlan4k->vid >= RTL8366S_NUM_VIDS || + vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || + vlan4k->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; + data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_TABLE_WRITE_BASE + i, + data[i]); + if (err) + return err; + } + + /* write table access control word */ + err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, + RTL8366S_TABLE_VLAN_WRITE_CTRL); + + return err; +} + +static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_read_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + &data[i]); + if (err) + return err; + } + + vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; + vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & + RTL8366S_VLAN_PRIORITY_MASK; + vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & + RTL8366S_VLAN_UNTAG_MASK; + vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & + RTL8366S_VLAN_FID_MASK; + + return 0; +} + +static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[2]; + int err; + int i; + + if (index >= RTL8366S_NUM_VLANS || + vlanmc->vid >= RTL8366S_NUM_VIDS || + vlanmc->priority > RTL8366S_PRIORITYMAX || + vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || + vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || + vlanmc->fid > RTL8366S_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | + ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << + RTL8366S_VLAN_PRIORITY_SHIFT); + data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | + ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << + RTL8366S_VLAN_UNTAG_SHIFT) | + ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << + RTL8366S_VLAN_FID_SHIFT); + + for (i = 0; i < 2; i++) { + err = rtl8366_smi_write_reg(smi, + RTL8366S_VLAN_MC_BASE(index) + i, + data[i]); + if (err) + return err; + } + + return 0; +} + +static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + &data); + if (err) + return err; + + *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & + RTL8366S_PORT_VLAN_CTRL_MASK; + + return 0; +} + +static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), + RTL8366S_PORT_VLAN_CTRL_MASK << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port), + (index & RTL8366S_PORT_VLAN_CTRL_MASK) << + RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); +} + +static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, + (enable) ? RTL8366S_SGCR_EN_VLAN : 0); +} + +static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, + 1, (enable) ? 1 : 0); +} + +static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8366S_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8366S_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), + (enable) ? 0 : (1 << port)); +} + +static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); +} + +static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); + + val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); + + return 0; +} + +static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->value.i >= 6) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, + RTL8366S_LED_BLINKRATE_MASK, + val->value.i); +} + +static int rtl8366s_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); + + val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); + + return 0; +} + +static int rtl8366s_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + char length_code; + + switch (val->value.i) { + case 0: + length_code = RTL8366S_SGCR_MAX_LENGTH_1522; + break; + case 1: + length_code = RTL8366S_SGCR_MAX_LENGTH_1536; + break; + case 2: + length_code = RTL8366S_SGCR_MAX_LENGTH_1552; + break; + case 3: + length_code = RTL8366S_SGCR_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, + RTL8366S_SGCR_MAX_LENGTH_MASK, + length_code); +} + +static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); + val->value.i = !data; + + return 0; +} + + +static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 portmask = 0; + int err = 0; + + if (!val->value.i) + portmask = RTL8366S_PORT_ALL; + + /* set learning for all ports */ + REG_WR(smi, RTL8366S_SSCR0, portmask); + + /* set auto ageing for all ports */ + REG_WR(smi, RTL8366S_SSCR1, portmask); + + return 0; +} + +static int rtl8366s_sw_get_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + int err; + + err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data); + if (err) + return err; + + val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366s_set_green(smi, val->value.i); +} + +static int rtl8366s_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8366S_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), + &data); + + if (port % 2) + data = data >> 8; + + link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); + link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); + link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); + link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); + + speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8366s_sw_set_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + u32 mask; + u32 reg; + + if (val->port_vlan >= RTL8366S_NUM_PORTS || + (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) + return -EINVAL; + + if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { + reg = RTL8366S_LED_BLINKRATE_REG; + mask = 0xF << 4; + data = val->value.i << 4; + } else { + reg = RTL8366S_LED_CTRL_REG; + mask = 0xF << (val->port_vlan * 4), + data = val->value.i << (val->port_vlan * 4); + } + + return rtl8366_smi_rmwr(smi, reg, mask, data); +} + +static int rtl8366s_sw_get_port_led(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + + if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); + val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; + + return 0; +} + +static int rtl8366s_sw_get_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int err; + u32 phyData; + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); + if (err) + return err; + + val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0; + + return 0; +} + +static int rtl8366s_sw_set_green_port(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i); +} + +static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + if (val->port_vlan >= RTL8366S_NUM_PORTS) + return -EINVAL; + + + return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, + 0, (1 << (val->port_vlan + 3))); +} + +static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID)); +} + +static struct switch_attr rtl8366s_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_learning", + .description = "Enable learning, enable aging", + .set = rtl8366s_sw_set_learning_enable, + .get = rtl8366s_sw_get_learning_enable, + .max = 1, + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8366s_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "blinkrate", + .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," + " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", + .set = rtl8366s_sw_set_blinkrate, + .get = rtl8366s_sw_get_blinkrate, + .max = 5 + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", + .set = rtl8366s_sw_set_max_length, + .get = rtl8366s_sw_get_max_length, + .max = 3, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_mode", + .description = "Get/Set the router green feature", + .set = rtl8366s_sw_set_green, + .get = rtl8366s_sw_get_green, + .max = 1, + }, +}; + +static struct switch_attr rtl8366s_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8366s_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, { + .type = SWITCH_TYPE_INT, + .name = "led", + .description = "Get/Set port group (0 - 3) led mode (0 - 15)", + .max = 15, + .set = rtl8366s_sw_set_port_led, + .get = rtl8366s_sw_get_port_led, + }, { + .type = SWITCH_TYPE_INT, + .name = "green_port", + .description = "Get/Set port green feature (0 - 1)", + .max = 1, + .set = rtl8366s_sw_set_green_port, + .get = rtl8366s_sw_get_green_port, + }, +}; + +static struct switch_attr rtl8366s_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8366S_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8366_ops = { + .attr_global = { + .attr = rtl8366s_globals, + .n_attr = ARRAY_SIZE(rtl8366s_globals), + }, + .attr_port = { + .attr = rtl8366s_port, + .n_attr = ARRAY_SIZE(rtl8366s_port), + }, + .attr_vlan = { + .attr = rtl8366s_vlan, + .n_attr = ARRAY_SIZE(rtl8366s_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8366s_sw_get_port_link, + .get_port_stats = rtl8366s_sw_get_port_stats, +}; + +static int rtl8366s_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8366S"; + dev->cpu_port = RTL8366S_PORT_NUM_CPU; + dev->ports = RTL8366S_NUM_PORTS; + dev->vlans = RTL8366S_NUM_VIDS; + dev->ops = &rtl8366_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); + /* flush write */ + (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); + + return err; +} + +static int rtl8366s_detect(struct rtl8366_smi *smi) +{ + u32 chip_id = 0; + u32 chip_ver = 0; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); + if (ret) { + dev_err(smi->parent, "unable to read chip id\n"); + return ret; + } + + switch (chip_id) { + case RTL8366S_CHIP_ID_8366: + break; + default: + dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, + &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%04x ver. %u chip found\n", + chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8366s_smi_ops = { + .detect = rtl8366s_detect, + .reset_chip = rtl8366s_reset_chip, + .setup = rtl8366s_setup, + + .mii_read = rtl8366s_mii_read, + .mii_write = rtl8366s_mii_write, + + .get_vlan_mc = rtl8366s_get_vlan_mc, + .set_vlan_mc = rtl8366s_set_vlan_mc, + .get_vlan_4k = rtl8366s_get_vlan_4k, + .set_vlan_4k = rtl8366s_set_vlan_4k, + .get_mc_index = rtl8366s_get_mc_index, + .set_mc_index = rtl8366s_set_mc_index, + .get_mib_counter = rtl8366_get_mib_counter, + .is_vlan_valid = rtl8366s_is_vlan_valid, + .enable_vlan = rtl8366s_enable_vlan, + .enable_vlan4k = rtl8366s_enable_vlan4k, + .enable_port = rtl8366s_enable_port, +}; + +static int rtl8366s_probe(struct platform_device *pdev) +{ + static int rtl8366_smi_version_printed; + struct rtl8366_smi *smi; + int err; + + if (!rtl8366_smi_version_printed++) + printk(KERN_NOTICE RTL8366S_DRIVER_DESC + " version " RTL8366S_DRIVER_VER"\n"); + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 10; + smi->cmd_read = 0xa9; + smi->cmd_write = 0xa8; + smi->ops = &rtl8366s_smi_ops; + smi->cpu_port = RTL8366S_PORT_NUM_CPU; + smi->num_ports = RTL8366S_NUM_PORTS; + smi->num_vlan_mc = RTL8366S_NUM_VLANS; + smi->mib_counters = rtl8366s_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8366s_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8366s_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8366s_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8366s_match[] = { + { .compatible = "realtek,rtl8366s" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8366s_match); +#endif + +static struct platform_driver rtl8366s_driver = { + .driver = { + .name = RTL8366S_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8366s_match), +#endif + }, + .probe = rtl8366s_probe, + .remove = rtl8366s_remove, +}; + +static int __init rtl8366s_module_init(void) +{ + return platform_driver_register(&rtl8366s_driver); +} +module_init(rtl8366s_module_init); + +static void __exit rtl8366s_module_exit(void) +{ + platform_driver_unregister(&rtl8366s_driver); +} +module_exit(rtl8366s_module_exit); + +MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); +MODULE_VERSION(RTL8366S_DRIVER_VER); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_AUTHOR("Antti Seppälä "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8367.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8367.c new file mode 100644 index 0000000000..9549961d76 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8367.c @@ -0,0 +1,1846 @@ +/* + * Platform driver for the Realtek RTL8367R/M ethernet switches + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367_PHY_ADDR_MAX 8 +#define RTL8367_PHY_REG_MAX 31 + +#define RTL8367_VID_MASK 0xffff +#define RTL8367_FID_MASK 0xfff +#define RTL8367_UNTAG_MASK 0xffff +#define RTL8367_MEMBER_MASK 0xffff + +#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 +#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367_TA_CTRL_REG 0x0500 +#define RTL8367_TA_CTRL_STATUS BIT(12) +#define RTL8367_TA_CTRL_METHOD BIT(5) +#define RTL8367_TA_CTRL_CMD_SHIFT 4 +#define RTL8367_TA_CTRL_CMD_READ 0 +#define RTL8367_TA_CTRL_CMD_WRITE 1 +#define RTL8367_TA_CTRL_TABLE_SHIFT 0 +#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367_TA_CTRL_TABLE_L2 4 +#define RTL8367_TA_CTRL_CVLAN_READ \ + ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) +#define RTL8367_TA_CTRL_CVLAN_WRITE \ + ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ + RTL8367_TA_CTRL_TABLE_CVLAN) + +#define RTL8367_TA_ADDR_REG 0x0501 +#define RTL8367_TA_ADDR_MASK 0x3fff + +#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) +#define RTL8367_TA_VLAN_DATA_SIZE 4 +#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK +#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 +#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_TA_VLAN_FID_SHIFT 0 +#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK +#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 +#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 +#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 +#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff + +#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) +#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f +#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) + +#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) +#define RTL8367_VLAN_MC_DATA_SIZE 4 +#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 +#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK +#define RTL8367_VLAN_MC_FID_SHIFT 0 +#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK +#define RTL8367_VLAN_MC_EVID_SHIFT 0 +#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK + +#define RTL8367_VLAN_CTRL_REG 0x07a8 +#define RTL8367_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367_VLAN_INGRESS_REG 0x07a9 + +#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) + +#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) + +#define RTL8367_MIB_ADDRESS_REG 0x1004 + +#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) +#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) +#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) +#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) +#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) +#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) + +#define RTL8367_MIB_COUNT 36 +#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 + +#define RTL8367_SWC0_REG 0x1200 +#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 +#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) +#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) +#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) +#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) +#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) +#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) + +#define RTL8367_CHIP_NUMBER_REG 0x1300 + +#define RTL8367_CHIP_VER_REG 0x1301 +#define RTL8367_CHIP_VER_RLVID_SHIFT 12 +#define RTL8367_CHIP_VER_RLVID_MASK 0xf +#define RTL8367_CHIP_VER_MCID_SHIFT 8 +#define RTL8367_CHIP_VER_MCID_MASK 0xf +#define RTL8367_CHIP_VER_BOID_SHIFT 4 +#define RTL8367_CHIP_VER_BOID_MASK 0xf + +#define RTL8367_CHIP_MODE_REG 0x1302 +#define RTL8367_CHIP_MODE_MASK 0x7 + +#define RTL8367_CHIP_DEBUG0_REG 0x1303 +#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367_DIS_REG 0x1305 +#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367_DIS_RGMII_MASK 0x7 + +#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367_DI_FORCE_MODE BIT(12) +#define RTL8367_DI_FORCE_NWAY BIT(7) +#define RTL8367_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367_DI_FORCE_LINK BIT(4) +#define RTL8367_DI_FORCE_DUPLEX BIT(2) +#define RTL8367_DI_FORCE_SPEED_MASK 3 +#define RTL8367_DI_FORCE_SPEED_10 0 +#define RTL8367_DI_FORCE_SPEED_100 1 +#define RTL8367_DI_FORCE_SPEED_1000 2 + +#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367_CHIP_RESET_REG 0x1322 +#define RTL8367_CHIP_RESET_SW BIT(1) +#define RTL8367_CHIP_RESET_HW BIT(0) + +#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) +#define RTL8367_PORT_STATUS_NWAY BIT(7) +#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) +#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) +#define RTL8367_PORT_STATUS_LINK BIT(4) +#define RTL8367_PORT_STATUS_DUPLEX BIT(2) +#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 +#define RTL8367_PORT_STATUS_SPEED_10 0 +#define RTL8367_PORT_STATUS_SPEED_100 1 +#define RTL8367_PORT_STATUS_SPEED_1000 2 + +#define RTL8367_RTL_NO_REG 0x13c0 +#define RTL8367_RTL_NO_8367R 0x3670 +#define RTL8367_RTL_NO_8367M 0x3671 + +#define RTL8367_RTL_VER_REG 0x13c1 +#define RTL8367_RTL_VER_MASK 0xf + +#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 +#define RTL8367_LED_MODE_REG 0x1b02 +#define RTL8367_LED_MODE_RATE_M 0x7 +#define RTL8367_LED_MODE_RATE_S 1 + +#define RTL8367_LED_CONFIG_REG 0x1b03 +#define RTL8367_LED_CONFIG_DATA_S 12 +#define RTL8367_LED_CONFIG_DATA_M 0x3 +#define RTL8367_LED_CONFIG_SEL BIT(14) +#define RTL8367_LED_CONFIG_LED_CFG_M 0xf + +#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 +#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 +#define RTL8367_PARA_LED_IO_EN_PMASK 0xff + +#define RTL8367_IA_CTRL_REG 0x1f00 +#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) +#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) +#define RTL8367_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367_IA_STATUS_REG 0x1f01 +#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367_IA_ADDRESS_REG 0x1f02 + +#define RTL8367_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367_IA_READ_DATA_REG 0x1f04 + +#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367_CPU_PORT_NUM 9 +#define RTL8367_NUM_PORTS 10 +#define RTL8367_NUM_VLANS 32 +#define RTL8367_NUM_LEDGROUPS 4 +#define RTL8367_NUM_VIDS 4096 +#define RTL8367_PRIORITYMAX 7 +#define RTL8367_FIDMAX 7 + +#define RTL8367_PORT_0 BIT(0) +#define RTL8367_PORT_1 BIT(1) +#define RTL8367_PORT_2 BIT(2) +#define RTL8367_PORT_3 BIT(3) +#define RTL8367_PORT_4 BIT(4) +#define RTL8367_PORT_5 BIT(5) +#define RTL8367_PORT_6 BIT(6) +#define RTL8367_PORT_7 BIT(7) +#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ +#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ + +#define RTL8367_PORTS_ALL \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ + RTL8367_PORT_E0) + +#define RTL8367_PORTS_ALL_BUT_CPU \ + (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ + RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ + RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) + +struct rtl8367_initval { + u16 reg; + u16 val; +}; + +#define RTL8367_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367_MIB_TXB_ID 20 /* IfOutOctets */ + +static struct rtl8366_mib_counter rtl8367_mib_counters[] = { + { 0, 0, 4, "IfInOctets" }, + { 0, 4, 2, "Dot3StatsFCSErrors" }, + { 0, 6, 2, "Dot3StatsSymbolErrors" }, + { 0, 8, 2, "Dot3InPauseFrames" }, + { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, + { 0, 12, 2, "EtherStatsFragments" }, + { 0, 14, 2, "EtherStatsJabbers" }, + { 0, 16, 2, "IfInUcastPkts" }, + { 0, 18, 2, "EtherStatsDropEvents" }, + { 0, 20, 4, "EtherStatsOctets" }, + + { 0, 24, 2, "EtherStatsUnderSizePkts" }, + { 0, 26, 2, "EtherOversizeStats" }, + { 0, 28, 2, "EtherStatsPkts64Octets" }, + { 0, 30, 2, "EtherStatsPkts65to127Octets" }, + { 0, 32, 2, "EtherStatsPkts128to255Octets" }, + { 0, 34, 2, "EtherStatsPkts256to511Octets" }, + { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, + { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, + { 0, 40, 2, "EtherStatsMulticastPkts" }, + { 0, 42, 2, "EtherStatsBroadcastPkts" }, + + { 0, 44, 4, "IfOutOctets" }, + + { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, + { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, + { 0, 52, 2, "Dot3sDeferredTransmissions" }, + { 0, 54, 2, "Dot3StatsLateCollisions" }, + { 0, 56, 2, "EtherStatsCollisions" }, + { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, + { 0, 60, 2, "Dot3OutPauseFrames" }, + { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, + { 0, 64, 2, "Dot1dTpPortInDiscards" }, + { 0, 66, 2, "IfOutUcastPkts" }, + { 0, 68, 2, "IfOutMulticastPkts" }, + { 0, 70, 2, "IfOutBroadcastPkts" }, + { 0, 72, 2, "OutOampduPkts" }, + { 0, 74, 2, "InOampduPkts" }, + { 0, 76, 2, "PktgenPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367_initval rtl8367_initvals_0_0[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, + {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, + {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, + {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, + {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, + {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, + {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, + {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, + {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, + {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, +}; + +static const struct rtl8367_initval rtl8367_initvals_0_1[] = { + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, + {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, + {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, + {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, + {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, + {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, + {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, + {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, + {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, + {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, + {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, + {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, + {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, + {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, + {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, + {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, + {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, + {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, + {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, + {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, + {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, + {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, + {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, + {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, + {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, + {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, + {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, + {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, + {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, + {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, + {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, + {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, + {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, + {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, + {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, + {0x133f, 0x0010}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_0[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, + {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, + {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, + {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, + {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, + {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, + {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, + {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, + {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, + {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, + {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, + {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_1_1[] = { + {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, + {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, + {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, + {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, + {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, + {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, + {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, + {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, + {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, + {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, + {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, + {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, + {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, + {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, + {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, + {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, + {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, + {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, + {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, + {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, + {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, + {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, + {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, + {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, + {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, + {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, + {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, + {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, + {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, + {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, + {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, + {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, + {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, + {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, + {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, + {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, + {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, + {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, + {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, + {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, + {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, + {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, + {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, + {0x1B03, 0x0876}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_0[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, + {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, + {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, + {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, + {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, + {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, + {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, + {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, + {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, + {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, + {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, + {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, + {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, + {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, +}; + +static const struct rtl8367_initval rtl8367_initvals_2_1[] = { + {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, + {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, + {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, + {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, + {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, + {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, + {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, + {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, + {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, + {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, + {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, + {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, + {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, + {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, + {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, + {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, + {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, + {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, + {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, + {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, + {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, + {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, + {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, + {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, + {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, + {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, + {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, + {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, + {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, + {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, + {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, + {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, + {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, + {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, + {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, + {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, + {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, + {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, + {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, + {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, + {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, + {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, + {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, + {0x130c, 0x0050}, +}; + +static int rtl8367_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if (data & RTL8367_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367_IA_ADDRESS_REG, + RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367_IA_CTRL_REG, + RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367_IA_STATUS_REG, &data); + if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + int err; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_0_0; + count = ARRAY_SIZE(rtl8367_initvals_0_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_0_1; + count = ARRAY_SIZE(rtl8367_initvals_0_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + err = rtl8367_write_initvals(smi, initvals, count); + if (err) + return err; + + /* TODO: complete this */ + + return 0; +} + +static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_1_0; + count = ARRAY_SIZE(rtl8367_initvals_1_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_1_1; + count = ARRAY_SIZE(rtl8367_initvals_1_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) +{ + const struct rtl8367_initval *initvals; + int count; + + switch (mode) { + case 0: + initvals = rtl8367_initvals_2_0; + count = ARRAY_SIZE(rtl8367_initvals_2_0); + break; + + case 1: + case 2: + initvals = rtl8367_initvals_2_1; + count = ARRAY_SIZE(rtl8367_initvals_2_1); + break; + + default: + dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); + return -ENODEV; + } + + return rtl8367_write_initvals(smi, initvals, count); +} + +static int rtl8367_init_regs(struct rtl8366_smi *smi) +{ + u32 data; + u32 rlvid; + u32 mode; + int err; + + REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); + + REG_RD(smi, RTL8367_CHIP_VER_REG, &data); + rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & + RTL8367_CHIP_VER_RLVID_MASK; + + REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); + mode = data & RTL8367_CHIP_MODE_MASK; + + switch (rlvid) { + case 0: + err = rtl8367_init_regs0(smi, mode); + break; + + case 1: + err = rtl8367_write_phy_reg(smi, 0, 31, 5); + if (err) + break; + + err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); + if (err) + break; + + err = rtl8367_read_phy_reg(smi, 0, 6, &data); + if (err) + break; + + if (data == 0x94eb) { + err = rtl8367_init_regs1(smi, mode); + } else if (data == 0x2104) { + err = rtl8367_init_regs2(smi, mode); + } else { + dev_err(smi->parent, "unknow phy data %04x\n", data); + return -ENODEV; + } + + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + err = -ENODEV; + break; + } + + return err; +} + +static int rtl8367_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); + msleep(RTL8367_RESET_DELAY); + + do { + REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); + if (!(data & RTL8367_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, + RTL8367_CHIP_DEBUG0_DUMMY0(id), + RTL8367_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367_DIS_REG, + RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), + mode << RTL8367_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_DI_FORCE_MODE | + RTL8367_DI_FORCE_NWAY | + RTL8367_DI_FORCE_TXPAUSE | + RTL8367_DI_FORCE_RXPAUSE | + RTL8367_DI_FORCE_LINK | + RTL8367_DI_FORCE_DUPLEX | + RTL8367_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | + (RTL8367_EXT_RGMXF_TXDELAY_MASK << + RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, + unsigned int group, u16 port_mask) +{ + u32 reg; + u32 s; + int err; + + port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; + s = (group % 2) * 8; + reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); + + REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); + + return 0; +} + +static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, + unsigned int mode) +{ + u16 mask; + u16 set; + int err; + + mode &= RTL8367_LED_CONFIG_DATA_M; + + mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | + RTL8367_LED_CONFIG_SEL; + set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + + return 0; +} + +static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, + unsigned int led, unsigned int cfg) +{ + u16 mask; + u16 set; + int err; + + mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | + RTL8367_LED_CONFIG_SEL; + set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); + + REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); + return 0; +} + +static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) +{ + int err; + + REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); + return 0; +} + +static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) +{ + u16 mask; + u16 set; + int err; + + mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; + set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; + REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, + RTL8367_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367_PORT_CFG_REG(i), + RTL8367_PORT_CFG_EGRESS_MODE_MASK << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, + RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << + RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); + + /* setup LEDs */ + err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); + if (err) + return err; + + err = rtl8367_led_group_set_mode(smi, 0); + if (err) + return err; + + err = rtl8367_led_op_select_parallel(smi); + if (err) + return err; + + err = rtl8367_led_blinkrate_set(smi, 1); + if (err) + return err; + + err = rtl8367_led_group_set_config(smi, 0, 2); + if (err) + return err; + + return 0; +} + +static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) + return -EINVAL; + + mib = &rtl8367_mib_counters[counter]; + addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); + + if (data & RTL8367_MIB_CTRL_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367_MIB_CTRL_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & + RTL8367_TA_VLAN_MEMBER_MASK; + vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & + RTL8367_TA_VLAN_FID_MASK; + vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & + RTL8367_TA_VLAN_UNTAG1_MASK; + vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & + RTL8367_TA_VLAN_UNTAG2_MASK) << 2; + + return 0; +} + +static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367_TA_VLAN_DATA_SIZE]; + int err; + int i; + + if (vlan4k->vid >= RTL8367_NUM_VIDS || + vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || + vlan4k->untag > RTL8367_UNTAG_MASK || + vlan4k->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << + RTL8367_TA_VLAN_MEMBER_SHIFT; + data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << + RTL8367_TA_VLAN_FID_SHIFT; + data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << + RTL8367_TA_VLAN_UNTAG1_SHIFT; + data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << + RTL8367_TA_VLAN_UNTAG2_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367_TA_ADDR_REG, + vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & + RTL8367_VLAN_MC_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & + RTL8367_VLAN_MC_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & + RTL8367_VLAN_MC_EVID_MASK; + + return 0; +} + +static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367_VLAN_MC_DATA_SIZE]; + int err; + int i; + + if (index >= RTL8367_NUM_VLANS || + vlanmc->vid >= RTL8367_NUM_VIDS || + vlanmc->priority > RTL8367_PRIORITYMAX || + vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || + vlanmc->untag > RTL8367_UNTAG_MASK || + vlanmc->fid > RTL8367_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << + RTL8367_VLAN_MC_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << + RTL8367_VLAN_MC_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << + RTL8367_VLAN_MC_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), + RTL8367_VLAN_PVID_CTRL_MASK << + RTL8367_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367_VLAN_PVID_CTRL_MASK) << + RTL8367_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, + RTL8367_VLAN_CTRL_ENABLE, + (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), + (enable) ? RTL8367_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, + RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); +} + +static int rtl8367_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); + + speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); + val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> + RTL8367_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, + RTL8367_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, + RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367_sw_set_max_length, + .get = rtl8367_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, { + .type = SWITCH_TYPE_INT, + .name = "fid", + .description = "Get/Set vlan FID", + .max = RTL8367_FIDMAX, + .set = rtl8366_sw_set_vlan_fid, + .get = rtl8366_sw_get_vlan_fid, + }, +}; + +static const struct switch_dev_ops rtl8367_sw_ops = { + .attr_global = { + .attr = rtl8367_globals, + .n_attr = ARRAY_SIZE(rtl8367_globals), + }, + .attr_port = { + .attr = rtl8367_port, + .n_attr = ARRAY_SIZE(rtl8367_port), + }, + .attr_vlan = { + .attr = rtl8367_vlan, + .n_attr = ARRAY_SIZE(rtl8367_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367_sw_get_port_link, + .get_port_stats = rtl8367_sw_get_port_stats, +}; + +static int rtl8367_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367"; + dev->cpu_port = RTL8367_CPU_PORT_NUM; + dev->ports = RTL8367_NUM_PORTS; + dev->vlans = RTL8367_NUM_VIDS; + dev->ops = &rtl8367_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367_detect(struct rtl8366_smi *smi) +{ + u32 rtl_no = 0; + u32 rtl_ver = 0; + char *chip_name; + int ret; + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); + if (ret) { + dev_err(smi->parent, "unable to read chip number\n"); + return ret; + } + + switch (rtl_no) { + case RTL8367_RTL_NO_8367R: + chip_name = "8367R"; + break; + case RTL8367_RTL_NO_8367M: + chip_name = "8367M"; + break; + default: + dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); + return -ENODEV; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); + if (ret) { + dev_err(smi->parent, "unable to read chip version\n"); + return ret; + } + + dev_info(smi->parent, "RTL%s ver. %u chip found\n", + chip_name, rtl_ver & RTL8367_RTL_VER_MASK); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367_smi_ops = { + .detect = rtl8367_detect, + .reset_chip = rtl8367_reset_chip, + .setup = rtl8367_setup, + + .mii_read = rtl8367_mii_read, + .mii_write = rtl8367_mii_write, + + .get_vlan_mc = rtl8367_get_vlan_mc, + .set_vlan_mc = rtl8367_set_vlan_mc, + .get_vlan_4k = rtl8367_get_vlan_4k, + .set_vlan_4k = rtl8367_set_vlan_4k, + .get_mc_index = rtl8367_get_mc_index, + .set_mc_index = rtl8367_set_mc_index, + .get_mib_counter = rtl8367_get_mib_counter, + .is_vlan_valid = rtl8367_is_vlan_valid, + .enable_vlan = rtl8367_enable_vlan, + .enable_vlan4k = rtl8367_enable_vlan4k, + .enable_port = rtl8367_enable_port, +}; + +static int rtl8367_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367_smi_ops; + smi->cpu_port = RTL8367_CPU_PORT_NUM; + smi->num_ports = RTL8367_NUM_PORTS; + smi->num_vlan_mc = RTL8367_NUM_VLANS; + smi->mib_counters = rtl8367_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367_match[] = { + { .compatible = "realtek,rtl8367" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367_match); +#endif + +static struct platform_driver rtl8367_driver = { + .driver = { + .name = RTL8367_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367_match), +#endif + }, + .probe = rtl8367_probe, + .remove = rtl8367_remove, + .shutdown = rtl8367_shutdown, +}; + +static int __init rtl8367_module_init(void) +{ + return platform_driver_register(&rtl8367_driver); +} +module_init(rtl8367_module_init); + +static void __exit rtl8367_module_exit(void) +{ + platform_driver_unregister(&rtl8367_driver); +} +module_exit(rtl8367_module_exit); + +MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/rtl8367b.c b/target/linux/generic/files-4.9/drivers/net/phy/rtl8367b.c new file mode 100644 index 0000000000..e6ea6509ae --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/rtl8367b.c @@ -0,0 +1,1612 @@ +/* + * Platform driver for the Realtek RTL8367R-VB ethernet switches + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtl8366_smi.h" + +#define RTL8367B_RESET_DELAY 1000 /* msecs*/ + +#define RTL8367B_PHY_ADDR_MAX 8 +#define RTL8367B_PHY_REG_MAX 31 + +#define RTL8367B_VID_MASK 0x3fff +#define RTL8367B_FID_MASK 0xf +#define RTL8367B_UNTAG_MASK 0xff +#define RTL8367B_MEMBER_MASK 0xff + +#define RTL8367B_PORT_MISC_CFG_REG(_p) (0x000e + 0x20 * (_p)) +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT 4 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK 0x3 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL 0 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP 1 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI 2 +#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL 3 + +#define RTL8367B_BYPASS_LINE_RATE_REG 0x03f7 + +#define RTL8367B_TA_CTRL_REG 0x0500 /*GOOD*/ +#define RTL8367B_TA_CTRL_SPA_SHIFT 8 +#define RTL8367B_TA_CTRL_SPA_MASK 0x7 +#define RTL8367B_TA_CTRL_METHOD BIT(4)/*GOOD*/ +#define RTL8367B_TA_CTRL_CMD_SHIFT 3 +#define RTL8367B_TA_CTRL_CMD_READ 0 +#define RTL8367B_TA_CTRL_CMD_WRITE 1 +#define RTL8367B_TA_CTRL_TABLE_SHIFT 0 /*GOOD*/ +#define RTL8367B_TA_CTRL_TABLE_ACLRULE 1 +#define RTL8367B_TA_CTRL_TABLE_ACLACT 2 +#define RTL8367B_TA_CTRL_TABLE_CVLAN 3 +#define RTL8367B_TA_CTRL_TABLE_L2 4 +#define RTL8367B_TA_CTRL_CVLAN_READ \ + ((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) +#define RTL8367B_TA_CTRL_CVLAN_WRITE \ + ((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \ + RTL8367B_TA_CTRL_TABLE_CVLAN) + +#define RTL8367B_TA_ADDR_REG 0x0501/*GOOD*/ +#define RTL8367B_TA_ADDR_MASK 0x3fff/*GOOD*/ + +#define RTL8367B_TA_LUT_REG 0x0502/*GOOD*/ + +#define RTL8367B_TA_WRDATA_REG(_x) (0x0510 + (_x))/*GOOD*/ +#define RTL8367B_TA_VLAN_NUM_WORDS 2 +#define RTL8367B_TA_VLAN_VID_MASK RTL8367B_VID_MASK +#define RTL8367B_TA_VLAN0_MEMBER_SHIFT 0 +#define RTL8367B_TA_VLAN0_MEMBER_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN0_UNTAG_SHIFT 8 +#define RTL8367B_TA_VLAN0_UNTAG_MASK RTL8367B_MEMBER_MASK +#define RTL8367B_TA_VLAN1_FID_SHIFT 0 +#define RTL8367B_TA_VLAN1_FID_MASK RTL8367B_FID_MASK + +#define RTL8367B_TA_RDDATA_REG(_x) (0x0520 + (_x))/*GOOD*/ + +#define RTL8367B_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_MASK 0x1f /*GOOD*/ +#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) /*GOOD*/ + +#define RTL8367B_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) /*GOOD*/ +#define RTL8367B_VLAN_MC_NUM_WORDS 4 /*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC0_MEMBER_MASK RTL8367B_MEMBER_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC1_FID_MASK RTL8367B_FID_MASK/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_SHIFT 0/*GOOD*/ +#define RTL8367B_VLAN_MC3_EVID_MASK RTL8367B_VID_MASK/*GOOD*/ + +#define RTL8367B_VLAN_CTRL_REG 0x07a8 /*GOOD*/ +#define RTL8367B_VLAN_CTRL_ENABLE BIT(0) + +#define RTL8367B_VLAN_INGRESS_REG 0x07a9 /*GOOD*/ + +#define RTL8367B_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) /*GOOD*/ + +#define RTL8367B_MIB_COUNTER_REG(_x) (0x1000 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_COUNTER_PORT_OFFSET 0x007c /*GOOD*/ + +#define RTL8367B_MIB_ADDRESS_REG 0x1004 /*GOOD*/ + +#define RTL8367B_MIB_CTRL0_REG(_x) (0x1005 + (_x)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK BIT(11) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_QM_RESET_MASK BIT(10) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_RESET_MASK BIT(1) /*GOOD*/ +#define RTL8367B_MIB_CTRL0_BUSY_MASK BIT(0) /*GOOD*/ + +#define RTL8367B_SWC0_REG 0x1200/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_SHIFT 13/*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH(_x) ((_x) << 13) /*GOOD*/ +#define RTL8367B_SWC0_MAX_LENGTH_MASK RTL8367B_SWC0_MAX_LENGTH(0x3) +#define RTL8367B_SWC0_MAX_LENGTH_1522 RTL8367B_SWC0_MAX_LENGTH(0) +#define RTL8367B_SWC0_MAX_LENGTH_1536 RTL8367B_SWC0_MAX_LENGTH(1) +#define RTL8367B_SWC0_MAX_LENGTH_1552 RTL8367B_SWC0_MAX_LENGTH(2) +#define RTL8367B_SWC0_MAX_LENGTH_16000 RTL8367B_SWC0_MAX_LENGTH(3) + +#define RTL8367B_CHIP_NUMBER_REG 0x1300/*GOOD*/ + +#define RTL8367B_CHIP_VER_REG 0x1301/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_SHIFT 12/*GOOD*/ +#define RTL8367B_CHIP_VER_RLVID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_SHIFT 8/*GOOD*/ +#define RTL8367B_CHIP_VER_MCID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_SHIFT 4/*GOOD*/ +#define RTL8367B_CHIP_VER_BOID_MASK 0xf/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_SHIFT 0/*GOOD*/ +#define RTL8367B_CHIP_VER_AFE_MASK 0x1/*GOOD*/ + +#define RTL8367B_CHIP_MODE_REG 0x1302 +#define RTL8367B_CHIP_MODE_MASK 0x7 + +#define RTL8367B_CHIP_DEBUG0_REG 0x1303 +#define RTL8367B_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) + +#define RTL8367B_CHIP_DEBUG1_REG 0x1304 + +#define RTL8367B_DIS_REG 0x1305 +#define RTL8367B_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) +#define RTL8367B_DIS_RGMII_SHIFT(_x) (4 * (_x)) +#define RTL8367B_DIS_RGMII_MASK 0x7 + +#define RTL8367B_EXT_RGMXF_REG(_x) (0x1306 + (_x)) +#define RTL8367B_EXT_RGMXF_DUMMY0_SHIFT 5 +#define RTL8367B_EXT_RGMXF_DUMMY0_MASK 0x7ff +#define RTL8367B_EXT_RGMXF_TXDELAY_SHIFT 3 +#define RTL8367B_EXT_RGMXF_TXDELAY_MASK 1 +#define RTL8367B_EXT_RGMXF_RXDELAY_MASK 0x7 + +#define RTL8367B_DI_FORCE_REG(_x) (0x1310 + (_x)) +#define RTL8367B_DI_FORCE_MODE BIT(12) +#define RTL8367B_DI_FORCE_NWAY BIT(7) +#define RTL8367B_DI_FORCE_TXPAUSE BIT(6) +#define RTL8367B_DI_FORCE_RXPAUSE BIT(5) +#define RTL8367B_DI_FORCE_LINK BIT(4) +#define RTL8367B_DI_FORCE_DUPLEX BIT(2) +#define RTL8367B_DI_FORCE_SPEED_MASK 3 +#define RTL8367B_DI_FORCE_SPEED_10 0 +#define RTL8367B_DI_FORCE_SPEED_100 1 +#define RTL8367B_DI_FORCE_SPEED_1000 2 + +#define RTL8367B_MAC_FORCE_REG(_x) (0x1312 + (_x)) + +#define RTL8367B_CHIP_RESET_REG 0x1322 /*GOOD*/ +#define RTL8367B_CHIP_RESET_SW BIT(1) /*GOOD*/ +#define RTL8367B_CHIP_RESET_HW BIT(0) /*GOOD*/ + +#define RTL8367B_PORT_STATUS_REG(_p) (0x1352 + (_p)) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_1000_SPI BIT(11) /*GOOD*/ +#define RTL8367B_PORT_STATUS_EN_100_SPI BIT(10)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY_FAULT BIT(9)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK_MASTER BIT(8)/*GOOD*/ +#define RTL8367B_PORT_STATUS_NWAY BIT(7)/*GOOD*/ +#define RTL8367B_PORT_STATUS_TXPAUSE BIT(6)/*GOOD*/ +#define RTL8367B_PORT_STATUS_RXPAUSE BIT(5)/*GOOD*/ +#define RTL8367B_PORT_STATUS_LINK BIT(4)/*GOOD*/ +#define RTL8367B_PORT_STATUS_DUPLEX BIT(2)/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_MASK 0x0003/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_10 0/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_100 1/*GOOD*/ +#define RTL8367B_PORT_STATUS_SPEED_1000 2/*GOOD*/ + +#define RTL8367B_RTL_MAGIC_ID_REG 0x13c2 +#define RTL8367B_RTL_MAGIC_ID_VAL 0x0249 + +#define RTL8367B_IA_CTRL_REG 0x1f00 +#define RTL8367B_IA_CTRL_RW(_x) ((_x) << 1) +#define RTL8367B_IA_CTRL_RW_READ RTL8367B_IA_CTRL_RW(0) +#define RTL8367B_IA_CTRL_RW_WRITE RTL8367B_IA_CTRL_RW(1) +#define RTL8367B_IA_CTRL_CMD_MASK BIT(0) + +#define RTL8367B_IA_STATUS_REG 0x1f01 +#define RTL8367B_IA_STATUS_PHY_BUSY BIT(2) +#define RTL8367B_IA_STATUS_SDS_BUSY BIT(1) +#define RTL8367B_IA_STATUS_MDX_BUSY BIT(0) + +#define RTL8367B_IA_ADDRESS_REG 0x1f02 +#define RTL8367B_IA_WRITE_DATA_REG 0x1f03 +#define RTL8367B_IA_READ_DATA_REG 0x1f04 + +#define RTL8367B_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) + +#define RTL8367B_NUM_MIB_COUNTERS 58 + +#define RTL8367B_CPU_PORT_NUM 5 +#define RTL8367B_NUM_PORTS 8 +#define RTL8367B_NUM_VLANS 32 +#define RTL8367B_NUM_VIDS 4096 +#define RTL8367B_PRIORITYMAX 7 +#define RTL8367B_FIDMAX 7 + +#define RTL8367B_PORT_0 BIT(0) +#define RTL8367B_PORT_1 BIT(1) +#define RTL8367B_PORT_2 BIT(2) +#define RTL8367B_PORT_3 BIT(3) +#define RTL8367B_PORT_4 BIT(4) +#define RTL8367B_PORT_E0 BIT(5) /* External port 0 */ +#define RTL8367B_PORT_E1 BIT(6) /* External port 1 */ +#define RTL8367B_PORT_E2 BIT(7) /* External port 2 */ + +#define RTL8367B_PORTS_ALL \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \ + RTL8367B_PORT_E1 | RTL8367B_PORT_E2) + +#define RTL8367B_PORTS_ALL_BUT_CPU \ + (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ + RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 | \ + RTL8367B_PORT_E2) + +struct rtl8367b_initval { + u16 reg; + u16 val; +}; + +#define RTL8367B_MIB_RXB_ID 0 /* IfInOctets */ +#define RTL8367B_MIB_TXB_ID 28 /* IfOutOctets */ + +static struct rtl8366_mib_counter +rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = { + {0, 0, 4, "ifInOctets" }, + {0, 4, 2, "dot3StatsFCSErrors" }, + {0, 6, 2, "dot3StatsSymbolErrors" }, + {0, 8, 2, "dot3InPauseFrames" }, + {0, 10, 2, "dot3ControlInUnknownOpcodes" }, + {0, 12, 2, "etherStatsFragments" }, + {0, 14, 2, "etherStatsJabbers" }, + {0, 16, 2, "ifInUcastPkts" }, + {0, 18, 2, "etherStatsDropEvents" }, + {0, 20, 2, "ifInMulticastPkts" }, + {0, 22, 2, "ifInBroadcastPkts" }, + {0, 24, 2, "inMldChecksumError" }, + {0, 26, 2, "inIgmpChecksumError" }, + {0, 28, 2, "inMldSpecificQuery" }, + {0, 30, 2, "inMldGeneralQuery" }, + {0, 32, 2, "inIgmpSpecificQuery" }, + {0, 34, 2, "inIgmpGeneralQuery" }, + {0, 36, 2, "inMldLeaves" }, + {0, 38, 2, "inIgmpLeaves" }, + + {0, 40, 4, "etherStatsOctets" }, + {0, 44, 2, "etherStatsUnderSizePkts" }, + {0, 46, 2, "etherOversizeStats" }, + {0, 48, 2, "etherStatsPkts64Octets" }, + {0, 50, 2, "etherStatsPkts65to127Octets" }, + {0, 52, 2, "etherStatsPkts128to255Octets" }, + {0, 54, 2, "etherStatsPkts256to511Octets" }, + {0, 56, 2, "etherStatsPkts512to1023Octets" }, + {0, 58, 2, "etherStatsPkts1024to1518Octets" }, + + {0, 60, 4, "ifOutOctets" }, + {0, 64, 2, "dot3StatsSingleCollisionFrames" }, + {0, 66, 2, "dot3StatMultipleCollisionFrames" }, + {0, 68, 2, "dot3sDeferredTransmissions" }, + {0, 70, 2, "dot3StatsLateCollisions" }, + {0, 72, 2, "etherStatsCollisions" }, + {0, 74, 2, "dot3StatsExcessiveCollisions" }, + {0, 76, 2, "dot3OutPauseFrames" }, + {0, 78, 2, "ifOutDiscards" }, + {0, 80, 2, "dot1dTpPortInDiscards" }, + {0, 82, 2, "ifOutUcastPkts" }, + {0, 84, 2, "ifOutMulticastPkts" }, + {0, 86, 2, "ifOutBroadcastPkts" }, + {0, 88, 2, "outOampduPkts" }, + {0, 90, 2, "inOampduPkts" }, + {0, 92, 2, "inIgmpJoinsSuccess" }, + {0, 94, 2, "inIgmpJoinsFail" }, + {0, 96, 2, "inMldJoinsSuccess" }, + {0, 98, 2, "inMldJoinsFail" }, + {0, 100, 2, "inReportSuppressionDrop" }, + {0, 102, 2, "inLeaveSuppressionDrop" }, + {0, 104, 2, "outIgmpReports" }, + {0, 106, 2, "outIgmpLeaves" }, + {0, 108, 2, "outIgmpGeneralQuery" }, + {0, 110, 2, "outIgmpSpecificQuery" }, + {0, 112, 2, "outMldReports" }, + {0, 114, 2, "outMldLeaves" }, + {0, 116, 2, "outMldGeneralQuery" }, + {0, 118, 2, "outMldSpecificQuery" }, + {0, 120, 2, "inKnownMulticastPkts" }, +}; + +#define REG_RD(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_read_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_WR(_smi, _reg, _val) \ + do { \ + err = rtl8366_smi_write_reg(_smi, _reg, _val); \ + if (err) \ + return err; \ + } while (0) + +#define REG_RMW(_smi, _reg, _mask, _val) \ + do { \ + err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ + if (err) \ + return err; \ + } while (0) + +static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14}, + {0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002}, + {0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000}, + {0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000}, + {0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000}, + {0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013}, + {0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, + {0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02}, + {0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115}, + {0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007}, + {0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044}, + {0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1}, + {0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026}, + {0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, + {0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000}, + {0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E}, + {0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00}, + {0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01}, + {0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02}, + {0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03}, + {0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04}, + {0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05}, + {0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06}, + {0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00}, + {0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000}, + {0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015}, + {0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340}, + {0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E}, + {0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000}, + {0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E}, + {0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1}, + {0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E}, + {0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0}, + {0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0}, + {0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0}, + {0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0}, + {0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0}, + {0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0}, + {0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0}, + {0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2}, + {0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4}, + {0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4}, + {0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA}, + {0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA}, + {0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85}, + {0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B}, + {0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A}, + {0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A}, + {0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B}, + {0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF}, + {0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8}, + {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620}, + {0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225}, + {0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302}, + {0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1}, + {0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282}, + {0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6}, + {0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4}, + {0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305}, + {0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E}, + {0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E}, + {0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25}, + {0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B}, + {0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B}, + {0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0}, + {0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4}, + {0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23}, + {0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359}, + {0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC}, + {0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29}, + {0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1}, + {0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29}, + {0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE}, + {0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0}, + {0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, + {0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0}, + {0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84}, + {0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72}, + {0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC}, + {0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, + {0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1}, + {0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B}, + {0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7}, + {0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC}, + {0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602}, + {0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02}, + {0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602}, + {0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE}, + {0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD}, + {0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22}, + {0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC}, + {0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, + {0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F}, + {0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13}, + {0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87}, + {0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0}, + {0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0}, + {0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4}, + {0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12}, + {0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12}, + {0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, + {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146}, + {0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0}, + {0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F}, + {0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21}, + {0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF}, + {0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5}, + {0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10}, + {0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459}, + {0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F}, + {0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96}, + {0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF}, + {0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0}, + {0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F}, + {0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159}, + {0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C}, + {0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302}, + {0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34}, + {0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0}, + {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D}, + {0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A}, + {0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34}, + {0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34}, + {0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0}, + {0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D}, + {0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF}, + {0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498}, + {0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03}, + {0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207}, + {0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD}, + {0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31}, + {0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31}, + {0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284}, + {0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE}, + {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02}, + {0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4}, + {0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0}, + {0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1}, + {0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1}, + {0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, + {0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A}, + {0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0}, + {0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A}, + {0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1}, + {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C}, + {0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02}, + {0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA}, + {0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11}, + {0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04}, + {0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE}, + {0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE}, + {0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE}, + {0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34}, + {0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11}, + {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, + {0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, + {0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, + {0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0}, + {0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20}, + {0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21}, + {0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE}, + {0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284}, + {0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8}, + {0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402}, + {0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285}, + {0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B}, + {0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85}, + {0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D}, + {0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B}, + {0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8}, + {0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1}, + {0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5}, + {0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021}, + {0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000}, + {0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, + {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212}, + {0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26}, + {0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8}, + {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22}, + {0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, + {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464}, + {0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480}, + {0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142}, + {0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000}, + {0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010}, + {0x13EB, 0x11BB} +}; + +static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = { + {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA}, + {0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078}, + {0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00}, + {0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000}, + {0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000}, + {0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000}, + {0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030}, + {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E}, + {0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B}, + {0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00}, + {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E}, + {0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100}, + {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280}, + {0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, + {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, + {0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0}, + {0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1}, + {0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE}, + {0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1}, + {0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD}, + {0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7}, + {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20}, + {0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, + {0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A}, + {0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E}, + {0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5}, + {0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2}, + {0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC}, + {0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE}, + {0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, + {0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685}, + {0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85}, + {0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC}, + {0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140}, + {0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E}, + {0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22}, + {0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340}, + {0x133E, 0x000E}, {0x133F, 0x0010}, +}; + +static int rtl8367b_write_initvals(struct rtl8366_smi *smi, + const struct rtl8367b_initval *initvals, + int count) +{ + int err; + int i; + + for (i = 0; i < count; i++) + REG_WR(smi, initvals[i].reg, initvals[i].val); + + return 0; +} + +static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 *val) +{ + int timeout; + u32 data; + int err; + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send read command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy read timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + /* read data */ + REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val); + + dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, *val); + return 0; +} + +static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi, + u32 phy_addr, u32 phy_reg, u32 val) +{ + int timeout; + u32 data; + int err; + + dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", + phy_addr, phy_reg, val); + + if (phy_addr > RTL8367B_PHY_ADDR_MAX) + return -EINVAL; + + if (phy_reg > RTL8367B_PHY_REG_MAX) + return -EINVAL; + + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if (data & RTL8367B_IA_STATUS_PHY_BUSY) + return -ETIMEDOUT; + + /* preapre data */ + REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val); + + /* prepare address */ + REG_WR(smi, RTL8367B_IA_ADDRESS_REG, + RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); + + /* send write command */ + REG_WR(smi, RTL8367B_IA_CTRL_REG, + RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE); + + timeout = 5; + do { + REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); + if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) + break; + + if (timeout--) { + dev_err(smi->parent, "phy write timed out\n"); + return -ETIMEDOUT; + } + + udelay(1); + } while (1); + + return 0; +} + +static int rtl8367b_init_regs(struct rtl8366_smi *smi) +{ + const struct rtl8367b_initval *initvals; + u32 chip_ver; + u32 rlvid; + int count; + int err; + + REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL); + REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + + rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) & + RTL8367B_CHIP_VER_RLVID_MASK; + + switch (rlvid) { + case 0: + initvals = rtl8367r_vb_initvals_0; + count = ARRAY_SIZE(rtl8367r_vb_initvals_0); + break; + + case 1: + initvals = rtl8367r_vb_initvals_1; + count = ARRAY_SIZE(rtl8367r_vb_initvals_1); + break; + + default: + dev_err(smi->parent, "unknow rlvid %u\n", rlvid); + return -ENODEV; + } + + /* TODO: disable RLTP */ + + return rtl8367b_write_initvals(smi, initvals, count); +} + +static int rtl8367b_reset_chip(struct rtl8366_smi *smi) +{ + int timeout = 10; + int err; + u32 data; + + REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW); + msleep(RTL8367B_RESET_DELAY); + + do { + REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data); + if (!(data & RTL8367B_CHIP_RESET_HW)) + break; + + msleep(1); + } while (--timeout); + + if (!timeout) { + dev_err(smi->parent, "chip reset timed out\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id, + enum rtl8367_extif_mode mode) +{ + int err; + + /* set port mode */ + switch (mode) { + case RTL8367_EXTIF_MODE_RGMII: + case RTL8367_EXTIF_MODE_RGMII_33V: + REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367); + REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777); + break; + + case RTL8367_EXTIF_MODE_TMII_MAC: + case RTL8367_EXTIF_MODE_TMII_PHY: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), BIT((id + 1) % 2)); + break; + + case RTL8367_EXTIF_MODE_GMII: + REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG, + RTL8367B_CHIP_DEBUG0_DUMMY0(id), + RTL8367B_CHIP_DEBUG0_DUMMY0(id)); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6)); + break; + + case RTL8367_EXTIF_MODE_MII_MAC: + case RTL8367_EXTIF_MODE_MII_PHY: + case RTL8367_EXTIF_MODE_DISABLED: + REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, + BIT((id + 1) % 2), 0); + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0); + break; + + default: + dev_err(smi->parent, + "invalid mode for external interface %d\n", id); + return -EINVAL; + } + + REG_RMW(smi, RTL8367B_DIS_REG, + RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id), + mode << RTL8367B_DIS_RGMII_SHIFT(id)); + + return 0; +} + +static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id, + struct rtl8367_port_ability *pa) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_DI_FORCE_MODE | + RTL8367B_DI_FORCE_NWAY | + RTL8367B_DI_FORCE_TXPAUSE | + RTL8367B_DI_FORCE_RXPAUSE | + RTL8367B_DI_FORCE_LINK | + RTL8367B_DI_FORCE_DUPLEX | + RTL8367B_DI_FORCE_SPEED_MASK); + + val = pa->speed; + val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0; + val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0; + val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0; + val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0; + val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0; + val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0; + + REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, + unsigned txdelay, unsigned rxdelay) +{ + u32 mask; + u32 val; + int err; + + mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK | + (RTL8367B_EXT_RGMXF_TXDELAY_MASK << + RTL8367B_EXT_RGMXF_TXDELAY_SHIFT)); + + val = rxdelay; + val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT; + + REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val); + + return 0; +} + +static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id, + struct rtl8367_extif_config *cfg) +{ + enum rtl8367_extif_mode mode; + int err; + + mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; + + err = rtl8367b_extif_set_mode(smi, id, mode); + if (err) + return err; + + if (mode != RTL8367_EXTIF_MODE_DISABLED) { + err = rtl8367b_extif_set_force(smi, id, &cfg->ability); + if (err) + return err; + + err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay, + cfg->rxdelay); + if (err) + return err; + } + + return 0; +} + +#ifdef CONFIG_OF +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + struct rtl8367_extif_config *cfg; + const __be32 *prop; + int size; + int err; + + prop = of_get_property(smi->parent->of_node, name, &size); + if (!prop) + return rtl8367b_extif_init(smi, id, NULL); + + if (size != (9 * sizeof(*prop))) { + dev_err(smi->parent, "%s property is invalid\n", name); + return -EINVAL; + } + + cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + cfg->txdelay = be32_to_cpup(prop++); + cfg->rxdelay = be32_to_cpup(prop++); + cfg->mode = be32_to_cpup(prop++); + cfg->ability.force_mode = be32_to_cpup(prop++); + cfg->ability.txpause = be32_to_cpup(prop++); + cfg->ability.rxpause = be32_to_cpup(prop++); + cfg->ability.link = be32_to_cpup(prop++); + cfg->ability.duplex = be32_to_cpup(prop++); + cfg->ability.speed = be32_to_cpup(prop++); + + err = rtl8367b_extif_init(smi, id, cfg); + kfree(cfg); + + return err; +} +#else +static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, + const char *name) +{ + return -EINVAL; +} +#endif + +static int rtl8367b_setup(struct rtl8366_smi *smi) +{ + struct rtl8367_platform_data *pdata; + int err; + int i; + + pdata = smi->parent->platform_data; + + err = rtl8367b_init_regs(smi); + if (err) + return err; + + /* initialize external interfaces */ + if (smi->parent->of_node) { + err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0"); + if (err) + return err; + + err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1"); + if (err) + return err; + } else { + err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg); + if (err) + return err; + + err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg); + if (err) + return err; + } + + /* set maximum packet length to 1536 bytes */ + REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK, + RTL8367B_SWC0_MAX_LENGTH_1536); + + /* + * discard VLAN tagged packets if the port is not a member of + * the VLAN with which the packets is associated. + */ + REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL); + + /* + * Setup egress tag mode for each port. + */ + for (i = 0; i < RTL8367B_NUM_PORTS; i++) + REG_RMW(smi, + RTL8367B_PORT_MISC_CFG_REG(i), + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT, + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL << + RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT); + + return 0; +} + +static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter, + int port, unsigned long long *val) +{ + struct rtl8366_mib_counter *mib; + int offset; + int i; + int err; + u32 addr, data; + u64 mibvalue; + + if (port > RTL8367B_NUM_PORTS || + counter >= RTL8367B_NUM_MIB_COUNTERS) + return -EINVAL; + + mib = &rtl8367b_mib_counters[counter]; + addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset; + + /* + * Writing access counter address first + * then ASIC will prepare 64bits counter wait for being retrived + */ + REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2); + + /* read MIB control register */ + REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data); + + if (data & RTL8367B_MIB_CTRL0_BUSY_MASK) + return -EBUSY; + + if (data & RTL8367B_MIB_CTRL0_RESET_MASK) + return -EIO; + + if (mib->length == 4) + offset = 3; + else + offset = (mib->offset + 1) % 4; + + mibvalue = 0; + for (i = 0; i < mib->length; i++) { + REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data); + mibvalue = (mibvalue << 16) | (data & 0xFFFF); + } + + *val = mibvalue; + return 0; +} + +static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, + struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); + + if (vid >= RTL8367B_NUM_VIDS) + return -EINVAL; + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, vid); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ); + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]); + + vlan4k->vid = vid; + vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) & + RTL8367B_TA_VLAN0_MEMBER_MASK; + vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) & + RTL8367B_TA_VLAN0_UNTAG_MASK; + vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) & + RTL8367B_TA_VLAN1_FID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi, + const struct rtl8366_vlan_4k *vlan4k) +{ + u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; + int err; + int i; + + if (vlan4k->vid >= RTL8367B_NUM_VIDS || + vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK || + vlan4k->untag > RTL8367B_UNTAG_MASK || + vlan4k->fid > RTL8367B_FIDMAX) + return -EINVAL; + + memset(data, 0, sizeof(data)); + + data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) << + RTL8367B_TA_VLAN0_MEMBER_SHIFT; + data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) << + RTL8367B_TA_VLAN0_UNTAG_SHIFT; + data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) << + RTL8367B_TA_VLAN1_FID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]); + + /* write VID */ + REG_WR(smi, RTL8367B_TA_ADDR_REG, + vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK); + + /* write table access control word */ + REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE); + + return 0; +} + +static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index, + struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); + + if (index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]); + + vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) & + RTL8367B_VLAN_MC0_MEMBER_MASK; + vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) & + RTL8367B_VLAN_MC1_FID_MASK; + vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) & + RTL8367B_VLAN_MC3_EVID_MASK; + + return 0; +} + +static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index, + const struct rtl8366_vlan_mc *vlanmc) +{ + u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; + int err; + int i; + + if (index >= RTL8367B_NUM_VLANS || + vlanmc->vid >= RTL8367B_NUM_VIDS || + vlanmc->priority > RTL8367B_PRIORITYMAX || + vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK || + vlanmc->untag > RTL8367B_UNTAG_MASK || + vlanmc->fid > RTL8367B_FIDMAX) + return -EINVAL; + + data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) << + RTL8367B_VLAN_MC0_MEMBER_SHIFT; + data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) << + RTL8367B_VLAN_MC1_FID_SHIFT; + data[2] = 0; + data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) << + RTL8367B_VLAN_MC3_EVID_SHIFT; + + for (i = 0; i < ARRAY_SIZE(data); i++) + REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]); + + return 0; +} + +static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val) +{ + u32 data; + int err; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data); + + *val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) & + RTL8367B_VLAN_PVID_CTRL_MASK; + + return 0; +} + +static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index) +{ + if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), + RTL8367B_VLAN_PVID_CTRL_MASK << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port), + (index & RTL8367B_VLAN_PVID_CTRL_MASK) << + RTL8367B_VLAN_PVID_CTRL_SHIFT(port)); +} + +static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable) +{ + return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG, + RTL8367B_VLAN_CTRL_ENABLE, + (enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0); +} + +static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable) +{ + return 0; +} + +static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) +{ + unsigned max = RTL8367B_NUM_VLANS; + + if (smi->vlan4k_enabled) + max = RTL8367B_NUM_VIDS - 1; + + if (vlan == 0 || vlan >= max) + return 0; + + return 1; +} + +static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable) +{ + int err; + + REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port), + (enable) ? RTL8367B_PORTS_ALL : 0); + + return 0; +} + +static int rtl8367b_sw_reset_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0, + RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK); +} + +static int rtl8367b_sw_get_port_link(struct switch_dev *dev, + int port, + struct switch_port_link *link) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data = 0; + u32 speed; + + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data); + + link->link = !!(data & RTL8367B_PORT_STATUS_LINK); + if (!link->link) + return 0; + + link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX); + link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE); + link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE); + link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY); + + speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK); + switch (speed) { + case 0: + link->speed = SWITCH_PORT_SPEED_10; + break; + case 1: + link->speed = SWITCH_PORT_SPEED_100; + break; + case 2: + link->speed = SWITCH_PORT_SPEED_1000; + break; + default: + link->speed = SWITCH_PORT_SPEED_UNKNOWN; + break; + } + + return 0; +} + +static int rtl8367b_sw_get_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 data; + + rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data); + val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >> + RTL8367B_SWC0_MAX_LENGTH_SHIFT; + + return 0; +} + +static int rtl8367b_sw_set_max_length(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + u32 max_len; + + switch (val->value.i) { + case 0: + max_len = RTL8367B_SWC0_MAX_LENGTH_1522; + break; + case 1: + max_len = RTL8367B_SWC0_MAX_LENGTH_1536; + break; + case 2: + max_len = RTL8367B_SWC0_MAX_LENGTH_1552; + break; + case 3: + max_len = RTL8367B_SWC0_MAX_LENGTH_16000; + break; + default: + return -EINVAL; + } + + return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG, + RTL8367B_SWC0_MAX_LENGTH_MASK, max_len); +} + + +static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev, + const struct switch_attr *attr, + struct switch_val *val) +{ + struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); + int port; + + port = val->port_vlan; + if (port >= RTL8367B_NUM_PORTS) + return -EINVAL; + + return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0, + RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8)); +} + +static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port, + struct switch_port_stats *stats) +{ + return (rtl8366_sw_get_port_stats(dev, port, stats, + RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID)); +} + +static struct switch_attr rtl8367b_globals[] = { + { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable VLAN mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 1 + }, { + .type = SWITCH_TYPE_INT, + .name = "enable_vlan4k", + .description = "Enable VLAN 4K mode", + .set = rtl8366_sw_set_vlan_enable, + .get = rtl8366_sw_get_vlan_enable, + .max = 1, + .ofs = 2 + }, { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mibs", + .description = "Reset all MIB counters", + .set = rtl8367b_sw_reset_mibs, + }, { + .type = SWITCH_TYPE_INT, + .name = "max_length", + .description = "Get/Set the maximum length of valid packets" + "(0:1522, 1:1536, 2:1552, 3:16000)", + .set = rtl8367b_sw_set_max_length, + .get = rtl8367b_sw_get_max_length, + .max = 3, + } +}; + +static struct switch_attr rtl8367b_port[] = { + { + .type = SWITCH_TYPE_NOVAL, + .name = "reset_mib", + .description = "Reset single port MIB counters", + .set = rtl8367b_sw_reset_port_mibs, + }, { + .type = SWITCH_TYPE_STRING, + .name = "mib", + .description = "Get MIB counters for port", + .max = 33, + .set = NULL, + .get = rtl8366_sw_get_port_mib, + }, +}; + +static struct switch_attr rtl8367b_vlan[] = { + { + .type = SWITCH_TYPE_STRING, + .name = "info", + .description = "Get vlan information", + .max = 1, + .set = NULL, + .get = rtl8366_sw_get_vlan_info, + }, +}; + +static const struct switch_dev_ops rtl8367b_sw_ops = { + .attr_global = { + .attr = rtl8367b_globals, + .n_attr = ARRAY_SIZE(rtl8367b_globals), + }, + .attr_port = { + .attr = rtl8367b_port, + .n_attr = ARRAY_SIZE(rtl8367b_port), + }, + .attr_vlan = { + .attr = rtl8367b_vlan, + .n_attr = ARRAY_SIZE(rtl8367b_vlan), + }, + + .get_vlan_ports = rtl8366_sw_get_vlan_ports, + .set_vlan_ports = rtl8366_sw_set_vlan_ports, + .get_port_pvid = rtl8366_sw_get_port_pvid, + .set_port_pvid = rtl8366_sw_set_port_pvid, + .reset_switch = rtl8366_sw_reset_switch, + .get_port_link = rtl8367b_sw_get_port_link, + .get_port_stats = rtl8367b_sw_get_port_stats, +}; + +static int rtl8367b_switch_init(struct rtl8366_smi *smi) +{ + struct switch_dev *dev = &smi->sw_dev; + int err; + + dev->name = "RTL8367B"; + dev->cpu_port = RTL8367B_CPU_PORT_NUM; + dev->ports = RTL8367B_NUM_PORTS; + dev->vlans = RTL8367B_NUM_VIDS; + dev->ops = &rtl8367b_sw_ops; + dev->alias = dev_name(smi->parent); + + err = register_switch(dev, NULL); + if (err) + dev_err(smi->parent, "switch registration failed\n"); + + return err; +} + +static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi) +{ + unregister_switch(&smi->sw_dev); +} + +static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg) +{ + struct rtl8366_smi *smi = bus->priv; + u32 val = 0; + int err; + + err = rtl8367b_read_phy_reg(smi, addr, reg, &val); + if (err) + return 0xffff; + + return val; +} + +static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) +{ + struct rtl8366_smi *smi = bus->priv; + u32 t; + int err; + + err = rtl8367b_write_phy_reg(smi, addr, reg, val); + if (err) + return err; + + /* flush write */ + (void) rtl8367b_read_phy_reg(smi, addr, reg, &t); + + return err; +} + +static int rtl8367b_detect(struct rtl8366_smi *smi) +{ + const char *chip_name; + u32 chip_num; + u32 chip_ver; + u32 chip_mode; + int ret; + + /* TODO: improve chip detection */ + rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG, + RTL8367B_RTL_MAGIC_ID_VAL); + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip number"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip version"); + return ret; + } + + ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode); + if (ret) { + dev_err(smi->parent, "unable to read %s register\n", + "chip mode"); + return ret; + } + + switch (chip_ver) { + case 0x1000: + chip_name = "8367RB"; + break; + case 0x1010: + chip_name = "8367R-VB"; + break; + default: + dev_err(smi->parent, + "unknown chip num:%04x ver:%04x, mode:%04x\n", + chip_num, chip_ver, chip_mode); + return -ENODEV; + } + + dev_info(smi->parent, "RTL%s chip found\n", chip_name); + + return 0; +} + +static struct rtl8366_smi_ops rtl8367b_smi_ops = { + .detect = rtl8367b_detect, + .reset_chip = rtl8367b_reset_chip, + .setup = rtl8367b_setup, + + .mii_read = rtl8367b_mii_read, + .mii_write = rtl8367b_mii_write, + + .get_vlan_mc = rtl8367b_get_vlan_mc, + .set_vlan_mc = rtl8367b_set_vlan_mc, + .get_vlan_4k = rtl8367b_get_vlan_4k, + .set_vlan_4k = rtl8367b_set_vlan_4k, + .get_mc_index = rtl8367b_get_mc_index, + .set_mc_index = rtl8367b_set_mc_index, + .get_mib_counter = rtl8367b_get_mib_counter, + .is_vlan_valid = rtl8367b_is_vlan_valid, + .enable_vlan = rtl8367b_enable_vlan, + .enable_vlan4k = rtl8367b_enable_vlan4k, + .enable_port = rtl8367b_enable_port, +}; + +static int rtl8367b_probe(struct platform_device *pdev) +{ + struct rtl8366_smi *smi; + int err; + + smi = rtl8366_smi_probe(pdev); + if (!smi) + return -ENODEV; + + smi->clk_delay = 1500; + smi->cmd_read = 0xb9; + smi->cmd_write = 0xb8; + smi->ops = &rtl8367b_smi_ops; + smi->cpu_port = RTL8367B_CPU_PORT_NUM; + smi->num_ports = RTL8367B_NUM_PORTS; + smi->num_vlan_mc = RTL8367B_NUM_VLANS; + smi->mib_counters = rtl8367b_mib_counters; + smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters); + + err = rtl8366_smi_init(smi); + if (err) + goto err_free_smi; + + platform_set_drvdata(pdev, smi); + + err = rtl8367b_switch_init(smi); + if (err) + goto err_clear_drvdata; + + return 0; + + err_clear_drvdata: + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + err_free_smi: + kfree(smi); + return err; +} + +static int rtl8367b_remove(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) { + rtl8367b_switch_cleanup(smi); + platform_set_drvdata(pdev, NULL); + rtl8366_smi_cleanup(smi); + kfree(smi); + } + + return 0; +} + +static void rtl8367b_shutdown(struct platform_device *pdev) +{ + struct rtl8366_smi *smi = platform_get_drvdata(pdev); + + if (smi) + rtl8367b_reset_chip(smi); +} + +#ifdef CONFIG_OF +static const struct of_device_id rtl8367b_match[] = { + { .compatible = "realtek,rtl8367b" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rtl8367b_match); +#endif + +static struct platform_driver rtl8367b_driver = { + .driver = { + .name = RTL8367B_DRIVER_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_OF + .of_match_table = of_match_ptr(rtl8367b_match), +#endif + }, + .probe = rtl8367b_probe, + .remove = rtl8367b_remove, + .shutdown = rtl8367b_shutdown, +}; + +module_platform_driver(rtl8367b_driver); + +MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver"); +MODULE_AUTHOR("Gabor Juhos "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME); + diff --git a/target/linux/generic/files-4.9/drivers/net/phy/swconfig.c b/target/linux/generic/files-4.9/drivers/net/phy/swconfig.c new file mode 100644 index 0000000000..e8a684742c --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/swconfig.c @@ -0,0 +1,1256 @@ +/* + * swconfig.c: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SWCONFIG_DEVNAME "switch%d" + +#include "swconfig_leds.c" + +MODULE_AUTHOR("Felix Fietkau "); +MODULE_LICENSE("GPL"); + +static int swdev_id; +static struct list_head swdevs; +static DEFINE_MUTEX(swdevs_lock); +struct swconfig_callback; + +struct swconfig_callback { + struct sk_buff *msg; + struct genlmsghdr *hdr; + struct genl_info *info; + int cmd; + + /* callback for filling in the message data */ + int (*fill)(struct swconfig_callback *cb, void *arg); + + /* callback for closing the message before sending it */ + int (*close)(struct swconfig_callback *cb, void *arg); + + struct nlattr *nest[4]; + int args[4]; +}; + +/* defaults */ + +static int +swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + int ret; + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + if (!dev->ops->get_vlan_ports) + return -EOPNOTSUPP; + + ret = dev->ops->get_vlan_ports(dev, val); + return ret; +} + +static int +swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port *ports = val->value.ports; + const struct switch_dev_ops *ops = dev->ops; + int i; + + if (val->port_vlan >= dev->vlans) + return -EINVAL; + + /* validate ports */ + if (val->len > dev->ports) + return -EINVAL; + + if (!ops->set_vlan_ports) + return -EOPNOTSUPP; + + for (i = 0; i < val->len; i++) { + if (ports[i].id >= dev->ports) + return -EINVAL; + + if (ops->set_port_pvid && + !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) + ops->set_port_pvid(dev, ports[i].id, val->port_vlan); + } + + return ops->set_vlan_ports(dev, val); +} + +static int +swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->set_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); +} + +static int +swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_pvid) + return -EOPNOTSUPP; + + return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); +} + +static int +swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + if (!dev->ops->set_port_link) + return -EOPNOTSUPP; + + return dev->ops->set_port_link(dev, val->port_vlan, val->value.link); +} + +static int +swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + struct switch_port_link *link = val->value.link; + + if (val->port_vlan >= dev->ports) + return -EINVAL; + + if (!dev->ops->get_port_link) + return -EOPNOTSUPP; + + memset(link, 0, sizeof(*link)); + return dev->ops->get_port_link(dev, val->port_vlan, link); +} + +static int +swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->apply_config) + return 0; + + return dev->ops->apply_config(dev); +} + +static int +swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, + struct switch_val *val) +{ + /* don't complain if not supported by the switch driver */ + if (!dev->ops->reset_switch) + return 0; + + return dev->ops->reset_switch(dev); +} + +enum global_defaults { + GLOBAL_APPLY, + GLOBAL_RESET, +}; + +enum vlan_defaults { + VLAN_PORTS, +}; + +enum port_defaults { + PORT_PVID, + PORT_LINK, +}; + +static struct switch_attr default_global[] = { + [GLOBAL_APPLY] = { + .type = SWITCH_TYPE_NOVAL, + .name = "apply", + .description = "Activate changes in the hardware", + .set = swconfig_apply_config, + }, + [GLOBAL_RESET] = { + .type = SWITCH_TYPE_NOVAL, + .name = "reset", + .description = "Reset the switch", + .set = swconfig_reset_switch, + } +}; + +static struct switch_attr default_port[] = { + [PORT_PVID] = { + .type = SWITCH_TYPE_INT, + .name = "pvid", + .description = "Primary VLAN ID", + .set = swconfig_set_pvid, + .get = swconfig_get_pvid, + }, + [PORT_LINK] = { + .type = SWITCH_TYPE_LINK, + .name = "link", + .description = "Get port link information", + .set = swconfig_set_link, + .get = swconfig_get_link, + } +}; + +static struct switch_attr default_vlan[] = { + [VLAN_PORTS] = { + .type = SWITCH_TYPE_PORTS, + .name = "ports", + .description = "VLAN port mapping", + .set = swconfig_set_vlan_ports, + .get = swconfig_get_vlan_ports, + }, +}; + +static const struct switch_attr * +swconfig_find_attr_by_name(const struct switch_attrlist *alist, + const char *name) +{ + int i; + + for (i = 0; i < alist->n_attr; i++) + if (strcmp(name, alist->attr[i].name) == 0) + return &alist->attr[i]; + + return NULL; +} + +static void swconfig_defaults_init(struct switch_dev *dev) +{ + const struct switch_dev_ops *ops = dev->ops; + + dev->def_global = 0; + dev->def_vlan = 0; + dev->def_port = 0; + + if (ops->get_vlan_ports || ops->set_vlan_ports) + set_bit(VLAN_PORTS, &dev->def_vlan); + + if (ops->get_port_pvid || ops->set_port_pvid) + set_bit(PORT_PVID, &dev->def_port); + + if (ops->get_port_link && + !swconfig_find_attr_by_name(&ops->attr_port, "link")) + set_bit(PORT_LINK, &dev->def_port); + + /* always present, can be no-op */ + set_bit(GLOBAL_APPLY, &dev->def_global); + set_bit(GLOBAL_RESET, &dev->def_global); +} + + +static struct genl_family switch_fam; + +static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { + [SWITCH_ATTR_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, + [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, + [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, + [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, +}; + +static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { + [SWITCH_PORT_ID] = { .type = NLA_U32 }, + [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, +}; + +static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = { + [SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG }, + [SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG }, + [SWITCH_LINK_SPEED] = { .type = NLA_U32 }, +}; + +static inline void +swconfig_lock(void) +{ + mutex_lock(&swdevs_lock); +} + +static inline void +swconfig_unlock(void) +{ + mutex_unlock(&swdevs_lock); +} + +static struct switch_dev * +swconfig_get_dev(struct genl_info *info) +{ + struct switch_dev *dev = NULL; + struct switch_dev *p; + int id; + + if (!info->attrs[SWITCH_ATTR_ID]) + goto done; + + id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); + swconfig_lock(); + list_for_each_entry(p, &swdevs, dev_list) { + if (id != p->id) + continue; + + dev = p; + break; + } + if (dev) + mutex_lock(&dev->sw_mutex); + else + pr_debug("device %d not found\n", id); + swconfig_unlock(); +done: + return dev; +} + +static inline void +swconfig_put_dev(struct switch_dev *dev) +{ + mutex_unlock(&dev->sw_mutex); +} + +static int +swconfig_dump_attr(struct swconfig_callback *cb, void *arg) +{ + struct switch_attr *op = arg; + struct genl_info *info = cb->info; + struct sk_buff *msg = cb->msg; + int id = cb->args[0]; + void *hdr; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) + goto nla_put_failure; + if (op->description) + if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, + op->description)) + goto nla_put_failure; + + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +/* spread multipart messages across multiple message buffers */ +static int +swconfig_send_multipart(struct swconfig_callback *cb, void *arg) +{ + struct genl_info *info = cb->info; + int restart = 0; + int err; + + do { + if (!cb->msg) { + cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (cb->msg == NULL) + goto error; + } + + if (!(cb->fill(cb, arg) < 0)) + break; + + /* fill failed, check if this was already the second attempt */ + if (restart) + goto error; + + /* try again in a new message, send the current one */ + restart = 1; + if (cb->close) { + if (cb->close(cb, arg) < 0) + goto error; + } + err = genlmsg_reply(cb->msg, info); + cb->msg = NULL; + if (err < 0) + goto error; + + } while (restart); + + return 0; + +error: + if (cb->msg) + nlmsg_free(cb->msg); + return -1; +} + +static int +swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + struct switch_dev *dev; + struct swconfig_callback cb; + int err = -EINVAL; + int i; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + switch (hdr->cmd) { + case SWITCH_CMD_LIST_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_LIST_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + break; + case SWITCH_CMD_LIST_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + break; + default: + WARN_ON(1); + goto out; + } + + memset(&cb, 0, sizeof(cb)); + cb.info = info; + cb.fill = swconfig_dump_attr; + for (i = 0; i < alist->n_attr; i++) { + if (alist->attr[i].disabled) + continue; + cb.args[0] = i; + err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); + if (err < 0) + goto error; + } + + /* defaults */ + for (i = 0; i < n_def; i++) { + if (!test_bit(i, def_active)) + continue; + cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; + err = swconfig_send_multipart(&cb, (void *) &def_list[i]); + if (err < 0) + goto error; + } + swconfig_put_dev(dev); + + if (!cb.msg) + return 0; + + return genlmsg_reply(cb.msg, info); + +error: + if (cb.msg) + nlmsg_free(cb.msg); +out: + swconfig_put_dev(dev); + return err; +} + +static const struct switch_attr * +swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, + struct switch_val *val) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attrlist *alist; + const struct switch_attr *attr = NULL; + unsigned int attr_id; + + /* defaults */ + struct switch_attr *def_list; + unsigned long *def_active; + int n_def; + + if (!info->attrs[SWITCH_ATTR_OP_ID]) + goto done; + + switch (hdr->cmd) { + case SWITCH_CMD_SET_GLOBAL: + case SWITCH_CMD_GET_GLOBAL: + alist = &dev->ops->attr_global; + def_list = default_global; + def_active = &dev->def_global; + n_def = ARRAY_SIZE(default_global); + break; + case SWITCH_CMD_SET_VLAN: + case SWITCH_CMD_GET_VLAN: + alist = &dev->ops->attr_vlan; + def_list = default_vlan; + def_active = &dev->def_vlan; + n_def = ARRAY_SIZE(default_vlan); + if (!info->attrs[SWITCH_ATTR_OP_VLAN]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); + if (val->port_vlan >= dev->vlans) + goto done; + break; + case SWITCH_CMD_SET_PORT: + case SWITCH_CMD_GET_PORT: + alist = &dev->ops->attr_port; + def_list = default_port; + def_active = &dev->def_port; + n_def = ARRAY_SIZE(default_port); + if (!info->attrs[SWITCH_ATTR_OP_PORT]) + goto done; + val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); + if (val->port_vlan >= dev->ports) + goto done; + break; + default: + WARN_ON(1); + goto done; + } + + if (!alist) + goto done; + + attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); + if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { + attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; + if (attr_id >= n_def) + goto done; + if (!test_bit(attr_id, def_active)) + goto done; + attr = &def_list[attr_id]; + } else { + if (attr_id >= alist->n_attr) + goto done; + attr = &alist->attr[attr_id]; + } + + if (attr->disabled) + attr = NULL; + +done: + if (!attr) + pr_debug("attribute lookup failed\n"); + val->attr = attr; + return attr; +} + +static int +swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, + struct switch_val *val, int max) +{ + struct nlattr *nla; + int rem; + + val->len = 0; + nla_for_each_nested(nla, head, rem) { + struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; + struct switch_port *port; + + if (val->len >= max) + return -EINVAL; + + port = &val->value.ports[val->len]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy)) +#else + if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, + port_policy, NULL)) +#endif + return -EINVAL; + + if (!tb[SWITCH_PORT_ID]) + return -EINVAL; + + port->id = nla_get_u32(tb[SWITCH_PORT_ID]); + if (tb[SWITCH_PORT_FLAG_TAGGED]) + port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); + val->len++; + } + + return 0; +} + +static int +swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla, + struct switch_port_link *link) +{ + struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy)) +#else + if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL)) +#endif + return -EINVAL; + + link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX]; + link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG]; + link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]); + + return 0; +} + +static int +swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) +{ + const struct switch_attr *attr; + struct switch_dev *dev; + struct switch_val val; + int err = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->set) + goto error; + + val.attr = attr; + switch (attr->type) { + case SWITCH_TYPE_NOVAL: + break; + case SWITCH_TYPE_INT: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) + goto error; + val.value.i = + nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); + break; + case SWITCH_TYPE_STRING: + if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) + goto error; + val.value.s = + nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); + break; + case SWITCH_TYPE_PORTS: + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + + /* TODO: implement multipart? */ + if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { + err = swconfig_parse_ports(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], + &val, dev->ports); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + case SWITCH_TYPE_LINK: + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + + if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) { + err = swconfig_parse_link(skb, + info->attrs[SWITCH_ATTR_OP_VALUE_LINK], + val.value.link); + if (err < 0) + goto error; + } else { + val.len = 0; + err = 0; + } + break; + default: + goto error; + } + + err = attr->set(dev, attr, &val); +error: + swconfig_put_dev(dev); + return err; +} + +static int +swconfig_close_portlist(struct swconfig_callback *cb, void *arg) +{ + if (cb->nest[0]) + nla_nest_end(cb->msg, cb->nest[0]); + return 0; +} + +static int +swconfig_send_port(struct swconfig_callback *cb, void *arg) +{ + const struct switch_port *port = arg; + struct nlattr *p = NULL; + + if (!cb->nest[0]) { + cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); + if (!cb->nest[0]) + return -1; + } + + p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); + if (!p) + goto error; + + if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) + goto nla_put_failure; + if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { + if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) + goto nla_put_failure; + } + + nla_nest_end(cb->msg, p); + return 0; + +nla_put_failure: + nla_nest_cancel(cb->msg, p); +error: + nla_nest_cancel(cb->msg, cb->nest[0]); + return -1; +} + +static int +swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, + const struct switch_val *val) +{ + struct swconfig_callback cb; + int err = 0; + int i; + + if (!val->value.ports) + return -EINVAL; + + memset(&cb, 0, sizeof(cb)); + cb.cmd = attr; + cb.msg = *msg; + cb.info = info; + cb.fill = swconfig_send_port; + cb.close = swconfig_close_portlist; + + cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); + for (i = 0; i < val->len; i++) { + err = swconfig_send_multipart(&cb, &val->value.ports[i]); + if (err) + goto done; + } + err = val->len; + swconfig_close_portlist(&cb, NULL); + *msg = cb.msg; + +done: + return err; +} + +static int +swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr, + const struct switch_port_link *link) +{ + struct nlattr *p = NULL; + int err = 0; + + p = nla_nest_start(msg, attr); + if (link->link) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK)) + goto nla_put_failure; + } + if (link->duplex) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX)) + goto nla_put_failure; + } + if (link->aneg) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG)) + goto nla_put_failure; + } + if (link->tx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW)) + goto nla_put_failure; + } + if (link->rx_flow) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW)) + goto nla_put_failure; + } + if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed)) + goto nla_put_failure; + if (link->eee & ADVERTISED_100baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET)) + goto nla_put_failure; + } + if (link->eee & ADVERTISED_1000baseT_Full) { + if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + + return err; + +nla_put_failure: + nla_nest_cancel(msg, p); + return -1; +} + +static int +swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) +{ + struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); + const struct switch_attr *attr; + struct switch_dev *dev; + struct sk_buff *msg = NULL; + struct switch_val val; + int err = -EINVAL; + int cmd = hdr->cmd; + + dev = swconfig_get_dev(info); + if (!dev) + return -EINVAL; + + memset(&val, 0, sizeof(val)); + attr = swconfig_lookup_attr(dev, info, &val); + if (!attr || !attr->get) + goto error; + + if (attr->type == SWITCH_TYPE_PORTS) { + val.value.ports = dev->portbuf; + memset(dev->portbuf, 0, + sizeof(struct switch_port) * dev->ports); + } else if (attr->type == SWITCH_TYPE_LINK) { + val.value.link = &dev->linkbuf; + memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); + } + + err = attr->get(dev, attr, &val); + if (err) + goto error; + + msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + goto error; + + hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, + 0, cmd); + if (IS_ERR(hdr)) + goto nla_put_failure; + + switch (attr->type) { + case SWITCH_TYPE_INT: + if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) + goto nla_put_failure; + break; + case SWITCH_TYPE_STRING: + if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) + goto nla_put_failure; + break; + case SWITCH_TYPE_PORTS: + err = swconfig_send_ports(&msg, info, + SWITCH_ATTR_OP_VALUE_PORTS, &val); + if (err < 0) + goto nla_put_failure; + break; + case SWITCH_TYPE_LINK: + err = swconfig_send_link(msg, info, + SWITCH_ATTR_OP_VALUE_LINK, val.value.link); + if (err < 0) + goto nla_put_failure; + break; + default: + pr_debug("invalid type in attribute\n"); + err = -EINVAL; + goto nla_put_failure; + } + genlmsg_end(msg, hdr); + err = msg->len; + if (err < 0) + goto nla_put_failure; + + swconfig_put_dev(dev); + return genlmsg_reply(msg, info); + +nla_put_failure: + if (msg) + nlmsg_free(msg); +error: + swconfig_put_dev(dev); + if (!err) + err = -ENOMEM; + return err; +} + +static int +swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, + const struct switch_dev *dev) +{ + struct nlattr *p = NULL, *m = NULL; + void *hdr; + int i; + + hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, + SWITCH_CMD_NEW_ATTR); + if (IS_ERR(hdr)) + return -1; + + if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) + goto nla_put_failure; + if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) + goto nla_put_failure; + + m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); + if (!m) + goto nla_put_failure; + for (i = 0; i < dev->ports; i++) { + p = nla_nest_start(msg, SWITCH_ATTR_PORTS); + if (!p) + continue; + if (dev->portmap[i].s) { + if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, + dev->portmap[i].s)) + goto nla_put_failure; + if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, + dev->portmap[i].virt)) + goto nla_put_failure; + } + nla_nest_end(msg, p); + } + nla_nest_end(msg, m); + genlmsg_end(msg, hdr); + return msg->len; +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +static int swconfig_dump_switches(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct switch_dev *dev; + int start = cb->args[0]; + int idx = 0; + + swconfig_lock(); + list_for_each_entry(dev, &swdevs, dev_list) { + if (++idx <= start) + continue; + if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + dev) < 0) + break; + } + swconfig_unlock(); + cb->args[0] = idx; + + return skb->len; +} + +static int +swconfig_done(struct netlink_callback *cb) +{ + return 0; +} + +static struct genl_ops swconfig_ops[] = { + { + .cmd = SWITCH_CMD_LIST_GLOBAL, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_VLAN, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_LIST_PORT, + .doit = swconfig_list_attrs, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_GLOBAL, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_VLAN, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_PORT, + .doit = swconfig_get_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_GLOBAL, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_VLAN, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_SET_PORT, + .flags = GENL_ADMIN_PERM, + .doit = swconfig_set_attr, + .policy = switch_policy, + }, + { + .cmd = SWITCH_CMD_GET_SWITCH, + .dumpit = swconfig_dump_switches, + .policy = switch_policy, + .done = swconfig_done, + } +}; + +static struct genl_family switch_fam = { +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + .id = GENL_ID_GENERATE, +#endif + .name = "switch", + .hdrsize = 0, + .version = 1, + .maxattr = SWITCH_ATTR_MAX, + .module = THIS_MODULE, + .ops = swconfig_ops, + .n_ops = ARRAY_SIZE(swconfig_ops), +}; + +#ifdef CONFIG_OF +void +of_switch_load_portmap(struct switch_dev *dev) +{ + struct device_node *port; + + if (!dev->of_node) + return; + + for_each_child_of_node(dev->of_node, port) { + const __be32 *prop; + const char *segment; + int size, phys; + + if (!of_device_is_compatible(port, "swconfig,port")) + continue; + + if (of_property_read_string(port, "swconfig,segment", &segment)) + continue; + + prop = of_get_property(port, "swconfig,portmap", &size); + if (!prop) + continue; + + if (size != (2 * sizeof(*prop))) { + pr_err("%s: failed to parse port mapping\n", + port->name); + continue; + } + + phys = be32_to_cpup(prop++); + if ((phys < 0) | (phys >= dev->ports)) { + pr_err("%s: physical port index out of range\n", + port->name); + continue; + } + + dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); + dev->portmap[phys].virt = be32_to_cpup(prop); + pr_debug("Found port: %s, physical: %d, virtual: %d\n", + segment, phys, dev->portmap[phys].virt); + } +} +#endif + +int +register_switch(struct switch_dev *dev, struct net_device *netdev) +{ + struct switch_dev *sdev; + const int max_switches = 8 * sizeof(unsigned long); + unsigned long in_use = 0; + int err; + int i; + + INIT_LIST_HEAD(&dev->dev_list); + if (netdev) { + dev->netdev = netdev; + if (!dev->alias) + dev->alias = netdev->name; + } + BUG_ON(!dev->alias); + + /* Make sure swdev_id doesn't overflow */ + if (swdev_id == INT_MAX) { + return -ENOMEM; + } + + if (dev->ports > 0) { + dev->portbuf = kzalloc(sizeof(struct switch_port) * + dev->ports, GFP_KERNEL); + if (!dev->portbuf) + return -ENOMEM; + dev->portmap = kzalloc(sizeof(struct switch_portmap) * + dev->ports, GFP_KERNEL); + if (!dev->portmap) { + kfree(dev->portbuf); + return -ENOMEM; + } + } + swconfig_defaults_init(dev); + mutex_init(&dev->sw_mutex); + swconfig_lock(); + dev->id = ++swdev_id; + + list_for_each_entry(sdev, &swdevs, dev_list) { + if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) + continue; + if (i < 0 || i > max_switches) + continue; + + set_bit(i, &in_use); + } + i = find_first_zero_bit(&in_use, max_switches); + + if (i == max_switches) { + swconfig_unlock(); + return -ENFILE; + } + +#ifdef CONFIG_OF + if (dev->ports) + of_switch_load_portmap(dev); +#endif + + /* fill device name */ + snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); + + list_add_tail(&dev->dev_list, &swdevs); + swconfig_unlock(); + + err = swconfig_create_led_trigger(dev); + if (err) + return err; + + return 0; +} +EXPORT_SYMBOL_GPL(register_switch); + +void +unregister_switch(struct switch_dev *dev) +{ + swconfig_destroy_led_trigger(dev); + kfree(dev->portbuf); + mutex_lock(&dev->sw_mutex); + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + mutex_unlock(&dev->sw_mutex); +} +EXPORT_SYMBOL_GPL(unregister_switch); + +int +switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link) +{ + if (WARN_ON(!dev->ops->phy_write16)) + return -ENOTSUPP; + + /* Generic implementation */ + if (link->aneg) { + dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); + dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); + } else { + u16 bmcr = 0; + + if (link->duplex) + bmcr |= BMCR_FULLDPLX; + + switch (link->speed) { + case SWITCH_PORT_SPEED_10: + break; + case SWITCH_PORT_SPEED_100: + bmcr |= BMCR_SPEED100; + break; + case SWITCH_PORT_SPEED_1000: + bmcr |= BMCR_SPEED1000; + break; + default: + return -ENOTSUPP; + } + + dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); + } + + return 0; +} + +static int __init +swconfig_init(void) +{ + INIT_LIST_HEAD(&swdevs); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) + return genl_register_family_with_ops(&switch_fam, swconfig_ops); +#else + return genl_register_family(&switch_fam); +#endif +} + +static void __exit +swconfig_exit(void) +{ + genl_unregister_family(&switch_fam); +} + +module_init(swconfig_init); +module_exit(swconfig_exit); diff --git a/target/linux/generic/files-4.9/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files-4.9/drivers/net/phy/swconfig_leds.c new file mode 100644 index 0000000000..91824b7cf6 --- /dev/null +++ b/target/linux/generic/files-4.9/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,556 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include +#include +#include +#include + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */ +#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */ +#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \ + SWCONFIG_LED_PORT_SPEED_10 | \ + SWCONFIG_LED_PORT_SPEED_100 | \ + SWCONFIG_LED_PORT_SPEED_1000) + +#define SWCONFIG_LED_MODE_LINK 0x01 +#define SWCONFIG_LED_MODE_TX 0x02 +#define SWCONFIG_LED_MODE_RX 0x04 +#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) +#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \ + SWCONFIG_LED_MODE_TX | \ + SWCONFIG_LED_MODE_RX) + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS]; + unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS]; + u8 link_speed[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; + u8 mode; + u8 speed_mask; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_set_brightness(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + int ret; + bool changed; + + ret = kstrtoul(buf, 0, &port_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + changed = (trig_data->port_mask != port_mask); + trig_data->port_mask = port_mask; + write_unlock(&trig_data->lock); + + if (changed) { + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + swconfig_trig_update_port_mask(led_cdev->trigger); + } + + return size; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u32 port_mask; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", port_mask); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +/* speed_mask file handler - display value */ +static ssize_t swconfig_trig_speed_mask_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + + read_lock(&trig_data->lock); + speed_mask = trig_data->speed_mask; + read_unlock(&trig_data->lock); + + sprintf(buf, "%#x\n", speed_mask); + + return strlen(buf) + 1; +} + +/* speed_mask file handler - store value */ +static ssize_t swconfig_trig_speed_mask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 speed_mask; + int ret; + + ret = kstrtou8(buf, 0, &speed_mask); + if (ret) + return ret; + + write_lock(&trig_data->lock); + trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL; + write_unlock(&trig_data->lock); + + return size; +} + +/* speed_mask special file */ +static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show, + swconfig_trig_speed_mask_store); + +static ssize_t swconfig_trig_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + u8 mode; + + read_lock(&trig_data->lock); + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + if (mode == 0) { + strcpy(buf, "none\n"); + } else { + if (mode & SWCONFIG_LED_MODE_LINK) + strcat(buf, "link "); + if (mode & SWCONFIG_LED_MODE_TX) + strcat(buf, "tx "); + if (mode & SWCONFIG_LED_MODE_RX) + strcat(buf, "rx "); + strcat(buf, "\n"); + } + + return strlen(buf)+1; +} + +static ssize_t swconfig_trig_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + char copybuf[128]; + int new_mode = -1; + char *p, *token; + + /* take a copy since we don't want to trash the inbound buffer when using strsep */ + strncpy(copybuf, buf, sizeof(copybuf)); + copybuf[sizeof(copybuf) - 1] = 0; + p = copybuf; + + while ((token = strsep(&p, " \t\n")) != NULL) { + if (!*token) + continue; + + if (new_mode < 0) + new_mode = 0; + + if (!strcmp(token, "none")) + new_mode = 0; + else if (!strcmp(token, "tx")) + new_mode |= SWCONFIG_LED_MODE_TX; + else if (!strcmp(token, "rx")) + new_mode |= SWCONFIG_LED_MODE_RX; + else if (!strcmp(token, "link")) + new_mode |= SWCONFIG_LED_MODE_LINK; + else + return -EINVAL; + } + + if (new_mode < 0) + return -EINVAL; + + write_lock(&trig_data->lock); + trig_data->mode = (u8)new_mode; + write_unlock(&trig_data->lock); + + return size; +} + +/* mode special file */ +static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show, + swconfig_trig_mode_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL; + trig_data->mode = SWCONFIG_LED_MODE_ALL; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + err = device_create_file(led_cdev->dev, &dev_attr_speed_mask); + if (err) + goto err_dev_free; + + err = device_create_file(led_cdev->dev, &dev_attr_mode); + if (err) + goto err_mode_free; + + return; + +err_mode_free: + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + +err_dev_free: + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + device_remove_file(led_cdev->dev, &dev_attr_speed_mask); + device_remove_file(led_cdev->dev, &dev_attr_mode); + kfree(trig_data); + } +} + +/* + * link off -> led off (can't be any other reason to turn it on) + * link on: + * mode link: led on by default only if speed matches, else off + * mode txrx: blink only if speed matches, else off + */ +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + u8 speed_mask, mode; + enum led_brightness led_base, led_blink; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + speed_mask = trig_data->speed_mask; + mode = trig_data->mode; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */ + } + else { + unsigned long traffic; + int speedok; /* link speed flag */ + int i; + + led_base = LED_FULL; + led_blink = LED_OFF; + traffic = 0; + speedok = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) { + if (sw_trig->link_speed[i] & speed_mask) { + traffic += ((mode & SWCONFIG_LED_MODE_TX) ? + sw_trig->port_tx_traffic[i] : 0) + + ((mode & SWCONFIG_LED_MODE_RX) ? + sw_trig->port_rx_traffic[i] : 0); + speedok = 1; + } + } + } + + if (speedok) { + /* At least one port speed matches speed_mask */ + if (!(mode & SWCONFIG_LED_MODE_LINK)) { + led_base = LED_OFF; + led_blink = LED_FULL; + } + + if (trig_data->prev_brightness != led_base) + swconfig_trig_set_brightness(trig_data, + led_base); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, + led_blink); + } else if (trig_data->prev_brightness != LED_OFF) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + sw_trig->link_speed[i] = 0; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) { + link |= port_bit; + switch (port_link.speed) { + case SWITCH_PORT_SPEED_UNKNOWN: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_NA; + break; + case SWITCH_PORT_SPEED_10: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_10; + break; + case SWITCH_PORT_SPEED_100: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_100; + break; + case SWITCH_PORT_SPEED_1000: + sw_trig->link_speed[i] = + SWCONFIG_LED_PORT_SPEED_1000; + break; + } + } + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_tx_traffic[i] = port_stats.tx_bytes; + sw_trig->port_rx_traffic[i] = port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files-4.9/include/linux/ar8216_platform.h b/target/linux/generic/files-4.9/include/linux/ar8216_platform.h new file mode 100644 index 0000000000..24bc442a26 --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/ar8216_platform.h @@ -0,0 +1,133 @@ +/* + * AR8216 switch driver platform data + * + * Copyright (C) 2012 Gabor Juhos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef AR8216_PLATFORM_H +#define AR8216_PLATFORM_H + +enum ar8327_pad_mode { + AR8327_PAD_NC = 0, + AR8327_PAD_MAC2MAC_MII, + AR8327_PAD_MAC2MAC_GMII, + AR8327_PAD_MAC_SGMII, + AR8327_PAD_MAC2PHY_MII, + AR8327_PAD_MAC2PHY_GMII, + AR8327_PAD_MAC_RGMII, + AR8327_PAD_PHY_GMII, + AR8327_PAD_PHY_RGMII, + AR8327_PAD_PHY_MII, +}; + +enum ar8327_clk_delay_sel { + AR8327_CLK_DELAY_SEL0 = 0, + AR8327_CLK_DELAY_SEL1, + AR8327_CLK_DELAY_SEL2, + AR8327_CLK_DELAY_SEL3, +}; + +struct ar8327_pad_cfg { + enum ar8327_pad_mode mode; + bool rxclk_sel; + bool txclk_sel; + bool pipe_rxclk_sel; + bool txclk_delay_en; + bool rxclk_delay_en; + bool sgmii_delay_en; + enum ar8327_clk_delay_sel txclk_delay_sel; + enum ar8327_clk_delay_sel rxclk_delay_sel; + bool mac06_exchange_dis; +}; + +enum ar8327_port_speed { + AR8327_PORT_SPEED_10 = 0, + AR8327_PORT_SPEED_100, + AR8327_PORT_SPEED_1000, +}; + +struct ar8327_port_cfg { + int force_link:1; + enum ar8327_port_speed speed; + int txpause:1; + int rxpause:1; + int duplex:1; +}; + +struct ar8327_sgmii_cfg { + u32 sgmii_ctrl; + bool serdes_aen; +}; + +struct ar8327_led_cfg { + u32 led_ctrl0; + u32 led_ctrl1; + u32 led_ctrl2; + u32 led_ctrl3; + bool open_drain; +}; + +enum ar8327_led_num { + AR8327_LED_PHY0_0 = 0, + AR8327_LED_PHY0_1, + AR8327_LED_PHY0_2, + AR8327_LED_PHY1_0, + AR8327_LED_PHY1_1, + AR8327_LED_PHY1_2, + AR8327_LED_PHY2_0, + AR8327_LED_PHY2_1, + AR8327_LED_PHY2_2, + AR8327_LED_PHY3_0, + AR8327_LED_PHY3_1, + AR8327_LED_PHY3_2, + AR8327_LED_PHY4_0, + AR8327_LED_PHY4_1, + AR8327_LED_PHY4_2, +}; + +enum ar8327_led_mode { + AR8327_LED_MODE_HW = 0, + AR8327_LED_MODE_SW, +}; + +struct ar8327_led_info { + const char *name; + const char *default_trigger; + bool active_low; + enum ar8327_led_num led_num; + enum ar8327_led_mode mode; +}; + +#define AR8327_LED_INFO(_led, _mode, _name) { \ + .name = (_name), \ + .led_num = AR8327_LED_ ## _led, \ + .mode = AR8327_LED_MODE_ ## _mode \ +} + +struct ar8327_platform_data { + struct ar8327_pad_cfg *pad0_cfg; + struct ar8327_pad_cfg *pad5_cfg; + struct ar8327_pad_cfg *pad6_cfg; + struct ar8327_sgmii_cfg *sgmii_cfg; + struct ar8327_port_cfg port0_cfg; + struct ar8327_port_cfg port6_cfg; + struct ar8327_led_cfg *led_cfg; + + int (*get_port_link)(unsigned port); + + unsigned num_leds; + const struct ar8327_led_info *leds; +}; + +#endif /* AR8216_PLATFORM_H */ + diff --git a/target/linux/generic/files-4.9/include/linux/ath5k_platform.h b/target/linux/generic/files-4.9/include/linux/ath5k_platform.h new file mode 100644 index 0000000000..ec85224528 --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/ath5k_platform.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * Copyright (c) 2010 Daniel Golle + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH5K_PLATFORM_H +#define _LINUX_ATH5K_PLATFORM_H + +#define ATH5K_PLAT_EEP_MAX_WORDS 2048 + +struct ath5k_platform_data { + u16 *eeprom_data; + u8 *macaddr; +}; + +#endif /* _LINUX_ATH5K_PLATFORM_H */ diff --git a/target/linux/generic/files-4.9/include/linux/ath9k_platform.h b/target/linux/generic/files-4.9/include/linux/ath9k_platform.h new file mode 100644 index 0000000000..f1f2ad419c --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/ath9k_platform.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2008 Atheros Communications Inc. + * Copyright (c) 2009 Gabor Juhos + * Copyright (c) 2009 Imre Kaloz + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LINUX_ATH9K_PLATFORM_H +#define _LINUX_ATH9K_PLATFORM_H + +#define ATH9K_PLAT_EEP_MAX_WORDS 2048 + +struct ath9k_platform_data { + const char *eeprom_name; + + u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS]; + u8 *macaddr; + + int led_pin; + u32 gpio_mask; + u32 gpio_val; + + u32 bt_active_pin; + u32 bt_priority_pin; + u32 wlan_active_pin; + + bool endian_check; + bool is_clk_25mhz; + bool tx_gain_buffalo; + bool disable_2ghz; + bool disable_5ghz; + bool led_active_high; + + int (*get_mac_revision)(void); + int (*external_reset)(void); + + bool use_eeprom; + + int num_leds; + const struct gpio_led *leds; + + unsigned num_btns; + const struct gpio_keys_button *btns; + unsigned btn_poll_interval; + + bool ubnt_hsr; +}; + +#endif /* _LINUX_ATH9K_PLATFORM_H */ diff --git a/target/linux/generic/files-4.9/include/linux/myloader.h b/target/linux/generic/files-4.9/include/linux/myloader.h new file mode 100644 index 0000000000..d89e415fba --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/myloader.h @@ -0,0 +1,121 @@ +/* + * Compex's MyLoader specific definitions + * + * Copyright (C) 2006-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _MYLOADER_H_ +#define _MYLOADER_H_ + +/* Myloader specific magic numbers */ +#define MYLO_MAGIC_SYS_PARAMS 0x20021107 +#define MYLO_MAGIC_PARTITIONS 0x20021103 +#define MYLO_MAGIC_BOARD_PARAMS 0x20021103 + +/* Vendor ID's (seems to be same as the PCI vendor ID's) */ +#define VENID_COMPEX 0x11F6 + +/* Devices based on the ADM5120 */ +#define DEVID_COMPEX_NP27G 0x0078 +#define DEVID_COMPEX_NP28G 0x044C +#define DEVID_COMPEX_NP28GHS 0x044E +#define DEVID_COMPEX_WP54Gv1C 0x0514 +#define DEVID_COMPEX_WP54G 0x0515 +#define DEVID_COMPEX_WP54AG 0x0546 +#define DEVID_COMPEX_WPP54AG 0x0550 +#define DEVID_COMPEX_WPP54G 0x0555 + +/* Devices based on the Atheros AR2317 */ +#define DEVID_COMPEX_NP25G 0x05E6 +#define DEVID_COMPEX_WPE53G 0x05DC + +/* Devices based on the Atheros AR71xx */ +#define DEVID_COMPEX_WP543 0x0640 +#define DEVID_COMPEX_WPE72 0x0672 + +/* Devices based on the IXP422 */ +#define DEVID_COMPEX_WP18 0x047E +#define DEVID_COMPEX_NP18A 0x0489 + +/* Other devices */ +#define DEVID_COMPEX_NP26G8M 0x03E8 +#define DEVID_COMPEX_NP26G16M 0x03E9 + +struct mylo_partition { + uint16_t flags; /* partition flags */ + uint16_t type; /* type of the partition */ + uint32_t addr; /* relative address of the partition from the + flash start */ + uint32_t size; /* size of the partition in bytes */ + uint32_t param; /* if this is the active partition, the + MyLoader load code to this address */ +}; + +#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition, + * MyLoader loads firmware from here */ +#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */ +#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */ +#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM + * before decompression */ +#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */ +#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */ + +#define PARTITION_TYPE_FREE 0 +#define PARTITION_TYPE_USED 1 + +#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the + partition table */ + +struct mylo_partition_table { + uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */ + uint32_t res0; /* unknown/unused */ + uint32_t res1; /* unknown/unused */ + uint32_t res2; /* unknown/unused */ + struct mylo_partition partitions[MYLO_MAX_PARTITIONS]; +}; + +struct mylo_partition_header { + uint32_t len; /* length of the partition data */ + uint32_t crc; /* CRC value of the partition data */ +}; + +struct mylo_system_params { + uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t mylo_ver; + uint16_t vid; /* Vendor ID */ + uint16_t did; /* Device ID */ + uint16_t svid; /* Sub Vendor ID */ + uint16_t sdid; /* Sub Device ID */ + uint32_t rev; /* device revision */ + uint32_t fwhi; + uint32_t fwlo; + uint32_t tftp_addr; + uint32_t prog_start; + uint32_t flash_size; /* size of boot FLASH in bytes */ + uint32_t dram_size; /* size of onboard RAM in bytes */ +}; + +struct mylo_eth_addr { + uint8_t mac[6]; + uint8_t csum[2]; +}; + +#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address + in the board parameters */ + +struct mylo_board_params { + uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */ + uint32_t res0; + uint32_t res1; + uint32_t res2; + struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT]; +}; + +#endif /* _MYLOADER_H_*/ diff --git a/target/linux/generic/files-4.9/include/linux/platform_data/adm6996-gpio.h b/target/linux/generic/files-4.9/include/linux/platform_data/adm6996-gpio.h new file mode 100644 index 0000000000..d5af9bbf6e --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/platform_data/adm6996-gpio.h @@ -0,0 +1,29 @@ +/* + * ADM6996 GPIO platform data + * + * Copyright (C) 2013 Hauke Mehrtens + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __PLATFORM_ADM6996_GPIO_H +#define __PLATFORM_ADM6996_GPIO_H + +#include + +enum adm6996_model { + ADM6996FC = 1, + ADM6996M = 2, + ADM6996L = 3, +}; + +struct adm6996_gpio_platform_data { + u8 eecs; + u8 eesk; + u8 eedi; + enum adm6996_model model; +}; + +#endif diff --git a/target/linux/generic/files-4.9/include/linux/platform_data/b53.h b/target/linux/generic/files-4.9/include/linux/platform_data/b53.h new file mode 100644 index 0000000000..78427417a1 --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/platform_data/b53.h @@ -0,0 +1,36 @@ +/* + * B53 platform data + * + * Copyright (C) 2013 Jonas Gorski + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __B53_H +#define __B53_H + +#include + +struct b53_platform_data { + u32 chip_id; + u16 enabled_ports; + + /* allow to specify an ethX alias */ + const char *alias; + + /* only used by MMAP'd driver */ + unsigned big_endian:1; + void __iomem *regs; +}; + +#endif diff --git a/target/linux/generic/files-4.9/include/linux/routerboot.h b/target/linux/generic/files-4.9/include/linux/routerboot.h new file mode 100644 index 0000000000..3cda858cf9 --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/routerboot.h @@ -0,0 +1,106 @@ +/* + * Mikrotik's RouterBOOT definitions + * + * Copyright (C) 2007-2008 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _ROUTERBOOT_H +#define _ROUTERBOOT_H + +#define RB_MAC_SIZE 6 + +/* + * Magic numbers + */ +#define RB_MAGIC_HARD 0x64726148 /* "Hard" */ +#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */ +#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */ + +#define RB_ID_TERMINATOR 0 + +/* + * ID values for Hardware settings + */ +#define RB_ID_HARD_01 1 +#define RB_ID_HARD_02 2 +#define RB_ID_FLASH_INFO 3 +#define RB_ID_MAC_ADDRESS_PACK 4 +#define RB_ID_BOARD_NAME 5 +#define RB_ID_BIOS_VERSION 6 +#define RB_ID_HARD_07 7 +#define RB_ID_SDRAM_TIMINGS 8 +#define RB_ID_DEVICE_TIMINGS 9 +#define RB_ID_SOFTWARE_ID 10 +#define RB_ID_SERIAL_NUMBER 11 +#define RB_ID_HARD_12 12 +#define RB_ID_MEMORY_SIZE 13 +#define RB_ID_MAC_ADDRESS_COUNT 14 +#define RB_ID_HW_OPTIONS 21 +#define RB_ID_WLAN_DATA 22 + +/* + * ID values for Software settings + */ +#define RB_ID_UART_SPEED 1 +#define RB_ID_BOOT_DELAY 2 +#define RB_ID_BOOT_DEVICE 3 +#define RB_ID_BOOT_KEY 4 +#define RB_ID_CPU_MODE 5 +#define RB_ID_FW_VERSION 6 +#define RB_ID_SOFT_07 7 +#define RB_ID_SOFT_08 8 +#define RB_ID_BOOT_PROTOCOL 9 +#define RB_ID_SOFT_10 10 +#define RB_ID_SOFT_11 11 + +/* + * UART_SPEED values + */ +#define RB_UART_SPEED_115200 0 +#define RB_UART_SPEED_57600 1 +#define RB_UART_SPEED_38400 2 +#define RB_UART_SPEED_19200 3 +#define RB_UART_SPEED_9600 4 +#define RB_UART_SPEED_4800 5 +#define RB_UART_SPEED_2400 6 +#define RB_UART_SPEED_1200 7 + +/* + * BOOT_DELAY values + */ +#define RB_BOOT_DELAY_0SEC 0 +#define RB_BOOT_DELAY_1SEC 1 +#define RB_BOOT_DELAY_2SEC 2 + +/* + * BOOT_DEVICE values + */ +#define RB_BOOT_DEVICE_ETHER 0 +#define RB_BOOT_DEVICE_NANDETH 1 +#define RB_BOOT_DEVICE_ETHONCE 2 +#define RB_BOOT_DEVICE_NANDONLY 3 + +/* + * BOOT_KEY values + */ +#define RB_BOOT_KEY_ANY 0 +#define RB_BOOT_KEY_DEL 1 + +/* + * CPU_MODE values + */ +#define RB_CPU_MODE_POWERSAVE 0 +#define RB_CPU_MODE_REGULAR 1 + +/* + * BOOT_PROTOCOL values + */ +#define RB_BOOT_PROTOCOL_BOOTP 0 +#define RB_BOOT_PROTOCOL_DHCP 1 + +#endif /* _ROUTERBOOT_H */ diff --git a/target/linux/generic/files-4.9/include/linux/rt2x00_platform.h b/target/linux/generic/files-4.9/include/linux/rt2x00_platform.h new file mode 100644 index 0000000000..e10377e21b --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/rt2x00_platform.h @@ -0,0 +1,23 @@ +/* + * Platform data definition for the rt2x00 driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + */ + +#ifndef _RT2X00_PLATFORM_H +#define _RT2X00_PLATFORM_H + +struct rt2x00_platform_data { + char *eeprom_file_name; + const u8 *mac_address; + + int disable_2ghz; + int disable_5ghz; +}; + +#endif /* _RT2X00_PLATFORM_H */ diff --git a/target/linux/generic/files-4.9/include/linux/rtl8366.h b/target/linux/generic/files-4.9/include/linux/rtl8366.h new file mode 100644 index 0000000000..e3ce8f5361 --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/rtl8366.h @@ -0,0 +1,42 @@ +/* + * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver + * + * Copyright (C) 2009-2010 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8366_H +#define _RTL8366_H + +#define RTL8366_DRIVER_NAME "rtl8366" +#define RTL8366S_DRIVER_NAME "rtl8366s" +#define RTL8366RB_DRIVER_NAME "rtl8366rb" + +struct rtl8366_smi; + +enum rtl8366_type { + RTL8366_TYPE_UNKNOWN, + RTL8366_TYPE_S, + RTL8366_TYPE_RB, +}; + +struct rtl8366_initval { + unsigned reg; + u16 val; +}; + +struct rtl8366_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(struct rtl8366_smi *smi, bool active); + + unsigned num_initvals; + struct rtl8366_initval *initvals; +}; + +enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata); + +#endif /* _RTL8366_H */ diff --git a/target/linux/generic/files-4.9/include/linux/rtl8367.h b/target/linux/generic/files-4.9/include/linux/rtl8367.h new file mode 100644 index 0000000000..855de6a5cc --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/rtl8367.h @@ -0,0 +1,60 @@ +/* + * Platform data definition for the Realtek RTL8367 ethernet switch driver + * + * Copyright (C) 2011 Gabor Juhos + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _RTL8367_H +#define _RTL8367_H + +#define RTL8367_DRIVER_NAME "rtl8367" +#define RTL8367B_DRIVER_NAME "rtl8367b" + +enum rtl8367_port_speed { + RTL8367_PORT_SPEED_10 = 0, + RTL8367_PORT_SPEED_100, + RTL8367_PORT_SPEED_1000, +}; + +struct rtl8367_port_ability { + int force_mode; + int nway; + int txpause; + int rxpause; + int link; + int duplex; + enum rtl8367_port_speed speed; +}; + +enum rtl8367_extif_mode { + RTL8367_EXTIF_MODE_DISABLED = 0, + RTL8367_EXTIF_MODE_RGMII, + RTL8367_EXTIF_MODE_MII_MAC, + RTL8367_EXTIF_MODE_MII_PHY, + RTL8367_EXTIF_MODE_TMII_MAC, + RTL8367_EXTIF_MODE_TMII_PHY, + RTL8367_EXTIF_MODE_GMII, + RTL8367_EXTIF_MODE_RGMII_33V, +}; + +struct rtl8367_extif_config { + unsigned int txdelay; + unsigned int rxdelay; + enum rtl8367_extif_mode mode; + struct rtl8367_port_ability ability; +}; + +struct rtl8367_platform_data { + unsigned gpio_sda; + unsigned gpio_sck; + void (*hw_reset)(bool active); + + struct rtl8367_extif_config *extif0_cfg; + struct rtl8367_extif_config *extif1_cfg; +}; + +#endif /* _RTL8367_H */ diff --git a/target/linux/generic/files-4.9/include/linux/switch.h b/target/linux/generic/files-4.9/include/linux/switch.h new file mode 100644 index 0000000000..4e6238470d --- /dev/null +++ b/target/linux/generic/files-4.9/include/linux/switch.h @@ -0,0 +1,179 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef _LINUX_SWITCH_H +#define _LINUX_SWITCH_H + +#include +#include + +struct switch_dev; +struct switch_op; +struct switch_val; +struct switch_attr; +struct switch_attrlist; +struct switch_led_trigger; + +int register_switch(struct switch_dev *dev, struct net_device *netdev); +void unregister_switch(struct switch_dev *dev); + +/** + * struct switch_attrlist - attribute list + * + * @n_attr: number of attributes + * @attr: pointer to the attributes array + */ +struct switch_attrlist { + int n_attr; + const struct switch_attr *attr; +}; + +enum switch_port_speed { + SWITCH_PORT_SPEED_UNKNOWN = 0, + SWITCH_PORT_SPEED_10 = 10, + SWITCH_PORT_SPEED_100 = 100, + SWITCH_PORT_SPEED_1000 = 1000, +}; + +struct switch_port_link { + bool link; + bool duplex; + bool aneg; + bool tx_flow; + bool rx_flow; + enum switch_port_speed speed; + /* in ethtool adv_t format */ + u32 eee; +}; + +struct switch_port_stats { + unsigned long long tx_bytes; + unsigned long long rx_bytes; +}; + +/** + * struct switch_dev_ops - switch driver operations + * + * @attr_global: global switch attribute list + * @attr_port: port attribute list + * @attr_vlan: vlan attribute list + * + * Callbacks: + * + * @get_vlan_ports: read the port list of a VLAN + * @set_vlan_ports: set the port list of a VLAN + * + * @get_port_pvid: get the primary VLAN ID of a port + * @set_port_pvid: set the primary VLAN ID of a port + * + * @apply_config: apply all changed settings to the switch + * @reset_switch: resetting the switch + */ +struct switch_dev_ops { + struct switch_attrlist attr_global, attr_port, attr_vlan; + + int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); + + int (*get_port_pvid)(struct switch_dev *dev, int port, int *val); + int (*set_port_pvid)(struct switch_dev *dev, int port, int val); + + int (*apply_config)(struct switch_dev *dev); + int (*reset_switch)(struct switch_dev *dev); + + int (*get_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*set_port_link)(struct switch_dev *dev, int port, + struct switch_port_link *link); + int (*get_port_stats)(struct switch_dev *dev, int port, + struct switch_port_stats *stats); + + int (*phy_read16)(struct switch_dev *dev, int addr, u8 reg, u16 *value); + int (*phy_write16)(struct switch_dev *dev, int addr, u8 reg, u16 value); +}; + +struct switch_dev { + struct device_node *of_node; + const struct switch_dev_ops *ops; + /* will be automatically filled */ + char devname[IFNAMSIZ]; + + const char *name; + /* NB: either alias or netdev must be set */ + const char *alias; + struct net_device *netdev; + + unsigned int ports; + unsigned int vlans; + unsigned int cpu_port; + + /* the following fields are internal for swconfig */ + unsigned int id; + struct list_head dev_list; + unsigned long def_global, def_port, def_vlan; + + struct mutex sw_mutex; + struct switch_port *portbuf; + struct switch_portmap *portmap; + struct switch_port_link linkbuf; + + char buf[128]; + +#ifdef CONFIG_SWCONFIG_LEDS + struct switch_led_trigger *led_trigger; +#endif +}; + +struct switch_port { + u32 id; + u32 flags; +}; + +struct switch_portmap { + u32 virt; + const char *s; +}; + +struct switch_val { + const struct switch_attr *attr; + unsigned int port_vlan; + unsigned int len; + union { + const char *s; + u32 i; + struct switch_port *ports; + struct switch_port_link *link; + } value; +}; + +struct switch_attr { + int disabled; + int type; + const char *name; + const char *description; + + int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); + + /* for driver internal use */ + int id; + int ofs; + int max; +}; + +int switch_generic_set_link(struct switch_dev *dev, int port, + struct switch_port_link *link); + +#endif /* _LINUX_SWITCH_H */ diff --git a/target/linux/generic/files-4.9/include/uapi/linux/switch.h b/target/linux/generic/files-4.9/include/uapi/linux/switch.h new file mode 100644 index 0000000000..ea449653fa --- /dev/null +++ b/target/linux/generic/files-4.9/include/uapi/linux/switch.h @@ -0,0 +1,119 @@ +/* + * switch.h: Switch configuration API + * + * Copyright (C) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _UAPI_LINUX_SWITCH_H +#define _UAPI_LINUX_SWITCH_H + +#include +#include +#include +#include +#ifndef __KERNEL__ +#include +#include +#include +#endif + +/* main attributes */ +enum { + SWITCH_ATTR_UNSPEC, + /* global */ + SWITCH_ATTR_TYPE, + /* device */ + SWITCH_ATTR_ID, + SWITCH_ATTR_DEV_NAME, + SWITCH_ATTR_ALIAS, + SWITCH_ATTR_NAME, + SWITCH_ATTR_VLANS, + SWITCH_ATTR_PORTS, + SWITCH_ATTR_PORTMAP, + SWITCH_ATTR_CPU_PORT, + /* attributes */ + SWITCH_ATTR_OP_ID, + SWITCH_ATTR_OP_TYPE, + SWITCH_ATTR_OP_NAME, + SWITCH_ATTR_OP_PORT, + SWITCH_ATTR_OP_VLAN, + SWITCH_ATTR_OP_VALUE_INT, + SWITCH_ATTR_OP_VALUE_STR, + SWITCH_ATTR_OP_VALUE_PORTS, + SWITCH_ATTR_OP_VALUE_LINK, + SWITCH_ATTR_OP_DESCRIPTION, + /* port lists */ + SWITCH_ATTR_PORT, + SWITCH_ATTR_MAX +}; + +enum { + /* port map */ + SWITCH_PORTMAP_PORTS, + SWITCH_PORTMAP_SEGMENT, + SWITCH_PORTMAP_VIRT, + SWITCH_PORTMAP_MAX +}; + +/* commands */ +enum { + SWITCH_CMD_UNSPEC, + SWITCH_CMD_GET_SWITCH, + SWITCH_CMD_NEW_ATTR, + SWITCH_CMD_LIST_GLOBAL, + SWITCH_CMD_GET_GLOBAL, + SWITCH_CMD_SET_GLOBAL, + SWITCH_CMD_LIST_PORT, + SWITCH_CMD_GET_PORT, + SWITCH_CMD_SET_PORT, + SWITCH_CMD_LIST_VLAN, + SWITCH_CMD_GET_VLAN, + SWITCH_CMD_SET_VLAN +}; + +/* data types */ +enum switch_val_type { + SWITCH_TYPE_UNSPEC, + SWITCH_TYPE_INT, + SWITCH_TYPE_STRING, + SWITCH_TYPE_PORTS, + SWITCH_TYPE_LINK, + SWITCH_TYPE_NOVAL, +}; + +/* port nested attributes */ +enum { + SWITCH_PORT_UNSPEC, + SWITCH_PORT_ID, + SWITCH_PORT_FLAG_TAGGED, + SWITCH_PORT_ATTR_MAX +}; + +/* link nested attributes */ +enum { + SWITCH_LINK_UNSPEC, + SWITCH_LINK_FLAG_LINK, + SWITCH_LINK_FLAG_DUPLEX, + SWITCH_LINK_FLAG_ANEG, + SWITCH_LINK_FLAG_TX_FLOW, + SWITCH_LINK_FLAG_RX_FLOW, + SWITCH_LINK_SPEED, + SWITCH_LINK_FLAG_EEE_100BASET, + SWITCH_LINK_FLAG_EEE_1000BASET, + SWITCH_LINK_ATTR_MAX, +}; + +#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000 + + +#endif /* _UAPI_LINUX_SWITCH_H */ diff --git a/target/linux/generic/files/Documentation/networking/adm6996.txt b/target/linux/generic/files/Documentation/networking/adm6996.txt deleted file mode 100644 index ab59f1df03..0000000000 --- a/target/linux/generic/files/Documentation/networking/adm6996.txt +++ /dev/null @@ -1,110 +0,0 @@ -------- - -ADM6996FC / ADM6996M switch chip driver - - -1. General information - - This driver supports the FC and M models only. The ADM6996F and L are - completely different chips. - - Support for the FC model is extremely limited at the moment. There is no VLAN - support as of yet. The driver will not offer an swconfig interface for the FC - chip. - -1.1 VLAN IDs - - It is possible to define 16 different VLANs. Every VLAN has an identifier, its - VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the - swconfig based configuration is very straightforward. To define two VLANs with - IDs 4 and 5, you can invoke, for example: - - # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' - # swconfig dev ethX vlan 5 set ports '0t 1t 5t' - - The swconfig framework will automatically invoke 'port Y set pvid Z' for every - port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In - this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port - is the VLAN ID associated with untagged packets coming in on that port. - - But if you wish to use VLAN IDs outside the range 0-15, this automatic - behaviour of the swconfig framework becomes a problem. The 16 VLANs that - swconfig can configure on the ADM6996 also have a "vid" setting. By default, - this is the same as the number of the VLAN entry, to make the simple behaviour - above possible. To still support a VLAN with a VLAN ID higher than 15 - (presumably because you are in a network where such VLAN IDs are already in - use), you can change the "vid" setting of the VLAN to anything in the range - 0-1023. But suppose you did the following: - - # swconfig dev ethX vlan 0 set vid 998 - # swconfig dev ethX vlan 0 set ports '0 2 5t' - - Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid - 0'. But the "pvid" should be set to 998, so you are responsible for manually - fixing this! - -1.2 VLAN filtering - - The switch is configured to apply source port filtering. This means that - packets are only accepted when the port the packets came in on is a member of - the VLAN the packet should go to. - - Only membership of a VLAN is tested, it does not matter whether it is a tagged - or untagged membership. - - For untagged packets, the destination VLAN is the Primary VLAN ID of the - incoming port. So if the PVID of a port is 0, but that port is not a member of - the VLAN with ID 0, this means that untagged packets on that port are dropped. - This can be used as a roundabout way of dropping untagged packets from a port, - a mode often referred to as "Admit only tagged packets". - -1.3 Reset - - The two supported chip models do not have a sofware-initiated reset. When the - driver is initialised, as well as when the 'reset' swconfig option is invoked, - the driver will set those registers it knows about and supports to the correct - default value. But there are a lot of registers in the chip that the driver - does not support. If something changed those registers, invoking 'reset' or - performing a warm reboot might still leave the chip in a "broken" state. Only - a hardware reset will bring it back in the default state. - -2. Technical details on PHYs and the ADM6996 - - From the viewpoint of the Linux kernel, it is common that an Ethernet adapter - can be seen as a separate MAC entity and a separate PHY entity. The PHY entity - can be queried and set through registers accessible via an MDIO bus. A PHY - normally has a single address on that bus, in the range 0 through 31. - - The ADM6996 has special-purpose registers in the range of PHYs 0 through 10. - Even though all these registers control a single ADM6996 chip, the Linux - kernel treats this as 11 separate PHYs. The driver will bind to these - addresses to prevent a different PHY driver from binding and corrupting these - registers. - - What Linux sees as the PHY on address 0 is meant for the Ethernet MAC - connected to the CPU port of the ADM6996 switch chip (port 5). This is the - Ethernet MAC you will use to send and receive data through the switch. - - The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of - the switch chip. These can be accessed with the Generic PHY driver, as the - registers have the common layout. - - If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC - needs to bind to PHY address 20 for the port to work correctly. - - The ADM6996 switch driver will reset the ports 0 through 3 on startup and when - 'reset' is invoked. This could clash with a different PHY driver if the kernel - binds a PHY driver to address 16 through 19. - - If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996 - driver will simply always report a connected 100 Mbit/s full-duplex link for - that PHY, and provide no other functionality. This is most likely not what you - want. So if you see a message in your log - - ethX: PHY overlaps ADM6996, providing fixed PHY yy. - - This is most likely an indication that ethX will not work properly, and your - kernel needs to be configured to attach a different PHY to that Ethernet MAC. - - Controlling the mapping between MACs and PHYs is usually done in platform- or - board-specific fixup code. The ADM6996 driver has no influence over this. diff --git a/target/linux/generic/files/arch/mips/fw/myloader/Makefile b/target/linux/generic/files/arch/mips/fw/myloader/Makefile deleted file mode 100644 index 34acfd01cc..0000000000 --- a/target/linux/generic/files/arch/mips/fw/myloader/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# -# Makefile for the Compex's MyLoader support on MIPS architecture -# - -lib-y += myloader.o diff --git a/target/linux/generic/files/arch/mips/fw/myloader/myloader.c b/target/linux/generic/files/arch/mips/fw/myloader/myloader.c deleted file mode 100644 index a26f9ad3fd..0000000000 --- a/target/linux/generic/files/arch/mips/fw/myloader/myloader.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Compex's MyLoader specific prom routines - * - * Copyright (C) 2007-2008 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include - -#include -#include - -#define SYS_PARAMS_ADDR KSEG1ADDR(0x80000800) -#define BOARD_PARAMS_ADDR KSEG1ADDR(0x80000A00) -#define PART_TABLE_ADDR KSEG1ADDR(0x80000C00) -#define BOOT_PARAMS_ADDR KSEG1ADDR(0x80000E00) - -static struct myloader_info myloader_info __initdata; -static int myloader_found __initdata; - -struct myloader_info * __init myloader_get_info(void) -{ - struct mylo_system_params *sysp; - struct mylo_board_params *boardp; - struct mylo_partition_table *parts; - - if (myloader_found) - return &myloader_info; - - sysp = (struct mylo_system_params *)(SYS_PARAMS_ADDR); - boardp = (struct mylo_board_params *)(BOARD_PARAMS_ADDR); - parts = (struct mylo_partition_table *)(PART_TABLE_ADDR); - - printk(KERN_DEBUG "MyLoader: sysp=%08x, boardp=%08x, parts=%08x\n", - sysp->magic, boardp->magic, parts->magic); - - /* Check for some magic numbers */ - if (sysp->magic != MYLO_MAGIC_SYS_PARAMS || - boardp->magic != MYLO_MAGIC_BOARD_PARAMS || - le32_to_cpu(parts->magic) != MYLO_MAGIC_PARTITIONS) - return NULL; - - printk(KERN_DEBUG "MyLoader: id=%04x:%04x, sub_id=%04x:%04x\n", - sysp->vid, sysp->did, sysp->svid, sysp->sdid); - - myloader_info.vid = sysp->vid; - myloader_info.did = sysp->did; - myloader_info.svid = sysp->svid; - myloader_info.sdid = sysp->sdid; - - memcpy(myloader_info.macs, boardp->addr, sizeof(myloader_info.macs)); - - myloader_found = 1; - - return &myloader_info; -} diff --git a/target/linux/generic/files/drivers/leds/ledtrig-netdev.c b/target/linux/generic/files/drivers/leds/ledtrig-netdev.c deleted file mode 100644 index 8d3249010d..0000000000 --- a/target/linux/generic/files/drivers/leds/ledtrig-netdev.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * LED Kernel Netdev Trigger - * - * Toggles the LED to reflect the link and traffic state of a named net device - * - * Copyright 2007 Oliver Jowett - * - * Derived from ledtrig-timer.c which is: - * Copyright 2005-2006 Openedhand Ltd. - * Author: Richard Purdie - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "leds.h" - -/* - * Configurable sysfs attributes: - * - * device_name - network device name to monitor - * - * interval - duration of LED blink, in milliseconds - * - * mode - either "none" (LED is off) or a space separated list of one or more of: - * link: LED's normal state reflects whether the link is up (has carrier) or not - * tx: LED blinks on transmitted data - * rx: LED blinks on receive data - * - * Some suggestions: - * - * Simple link status LED: - * $ echo netdev >someled/trigger - * $ echo eth0 >someled/device_name - * $ echo link >someled/mode - * - * Ethernet-style link/activity LED: - * $ echo netdev >someled/trigger - * $ echo eth0 >someled/device_name - * $ echo "link tx rx" >someled/mode - * - * Modem-style tx/rx LEDs: - * $ echo netdev >led1/trigger - * $ echo ppp0 >led1/device_name - * $ echo tx >led1/mode - * $ echo netdev >led2/trigger - * $ echo ppp0 >led2/device_name - * $ echo rx >led2/mode - * - */ - -#define MODE_LINK 1 -#define MODE_TX 2 -#define MODE_RX 4 - -struct led_netdev_data { - spinlock_t lock; - - struct delayed_work work; - struct notifier_block notifier; - - struct led_classdev *led_cdev; - struct net_device *net_dev; - - char device_name[IFNAMSIZ]; - unsigned interval; - unsigned mode; - unsigned link_up; - unsigned last_activity; -}; - -static void set_baseline_state(struct led_netdev_data *trigger_data) -{ - if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) - led_set_brightness(trigger_data->led_cdev, LED_FULL); - else - led_set_brightness(trigger_data->led_cdev, LED_OFF); - - if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) - schedule_delayed_work(&trigger_data->work, trigger_data->interval); -} - -static ssize_t led_device_name_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - - spin_lock_bh(&trigger_data->lock); - sprintf(buf, "%s\n", trigger_data->device_name); - spin_unlock_bh(&trigger_data->lock); - - return strlen(buf) + 1; -} - -static ssize_t led_device_name_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - - if (size >= IFNAMSIZ) - return -EINVAL; - - cancel_delayed_work_sync(&trigger_data->work); - - spin_lock_bh(&trigger_data->lock); - - strcpy(trigger_data->device_name, buf); - if (size > 0 && trigger_data->device_name[size-1] == '\n') - trigger_data->device_name[size-1] = 0; - trigger_data->link_up = 0; - trigger_data->last_activity = 0; - - if (trigger_data->device_name[0] != 0) { - /* check for existing device to update from */ - trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); - if (trigger_data->net_dev != NULL) - trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; - } - - set_baseline_state(trigger_data); - spin_unlock_bh(&trigger_data->lock); - - return size; -} - -static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); - -static ssize_t led_mode_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - - spin_lock_bh(&trigger_data->lock); - - if (trigger_data->mode == 0) { - strcpy(buf, "none\n"); - } else { - if (trigger_data->mode & MODE_LINK) - strcat(buf, "link "); - if (trigger_data->mode & MODE_TX) - strcat(buf, "tx "); - if (trigger_data->mode & MODE_RX) - strcat(buf, "rx "); - strcat(buf, "\n"); - } - - spin_unlock_bh(&trigger_data->lock); - - return strlen(buf)+1; -} - -static ssize_t led_mode_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - char copybuf[128]; - int new_mode = -1; - char *p, *token; - - /* take a copy since we don't want to trash the inbound buffer when using strsep */ - strncpy(copybuf, buf, sizeof(copybuf)); - copybuf[sizeof(copybuf) - 1] = 0; - p = copybuf; - - while ((token = strsep(&p, " \t\n")) != NULL) { - if (!*token) - continue; - - if (new_mode == -1) - new_mode = 0; - - if (!strcmp(token, "none")) - new_mode = 0; - else if (!strcmp(token, "tx")) - new_mode |= MODE_TX; - else if (!strcmp(token, "rx")) - new_mode |= MODE_RX; - else if (!strcmp(token, "link")) - new_mode |= MODE_LINK; - else - return -EINVAL; - } - - if (new_mode == -1) - return -EINVAL; - - cancel_delayed_work_sync(&trigger_data->work); - - spin_lock_bh(&trigger_data->lock); - trigger_data->mode = new_mode; - set_baseline_state(trigger_data); - spin_unlock_bh(&trigger_data->lock); - - return size; -} - -static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); - -static ssize_t led_interval_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - - spin_lock_bh(&trigger_data->lock); - sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); - spin_unlock_bh(&trigger_data->lock); - - return strlen(buf) + 1; -} - -static ssize_t led_interval_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - int ret = -EINVAL; - char *after; - unsigned long value = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - - if (isspace(*after)) - count++; - - /* impose some basic bounds on the timer interval */ - if (count == size && value >= 5 && value <= 10000) { - cancel_delayed_work_sync(&trigger_data->work); - - spin_lock_bh(&trigger_data->lock); - trigger_data->interval = msecs_to_jiffies(value); - set_baseline_state(trigger_data); /* resets timer */ - spin_unlock_bh(&trigger_data->lock); - - ret = count; - } - - return ret; -} - -static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); - -static int netdev_trig_notify(struct notifier_block *nb, - unsigned long evt, - void *dv) -{ - struct net_device *dev = netdev_notifier_info_to_dev((struct netdev_notifier_info *) dv); - struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); - - if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER && evt != NETDEV_CHANGENAME) - return NOTIFY_DONE; - - if (strcmp(dev->name, trigger_data->device_name)) - return NOTIFY_DONE; - - cancel_delayed_work_sync(&trigger_data->work); - - spin_lock_bh(&trigger_data->lock); - - if (evt == NETDEV_REGISTER || evt == NETDEV_CHANGENAME) { - if (trigger_data->net_dev != NULL) - dev_put(trigger_data->net_dev); - - dev_hold(dev); - trigger_data->net_dev = dev; - trigger_data->link_up = 0; - goto done; - } - - if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { - dev_put(trigger_data->net_dev); - trigger_data->net_dev = NULL; - goto done; - } - - /* UP / DOWN / CHANGE */ - - trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); - set_baseline_state(trigger_data); - -done: - spin_unlock_bh(&trigger_data->lock); - return NOTIFY_DONE; -} - -/* here's the real work! */ -static void netdev_trig_work(struct work_struct *work) -{ - struct led_netdev_data *trigger_data = container_of(work, struct led_netdev_data, work.work); - struct rtnl_link_stats64 *dev_stats; - unsigned new_activity; - struct rtnl_link_stats64 temp; - - if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { - /* we don't need to do timer work, just reflect link state. */ - led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); - return; - } - - dev_stats = dev_get_stats(trigger_data->net_dev, &temp); - new_activity = - ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + - ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); - - if (trigger_data->mode & MODE_LINK) { - /* base state is ON (link present) */ - /* if there's no link, we don't get this far and the LED is off */ - - /* OFF -> ON always */ - /* ON -> OFF on activity */ - if (trigger_data->led_cdev->brightness == LED_OFF) { - led_set_brightness(trigger_data->led_cdev, LED_FULL); - } else if (trigger_data->last_activity != new_activity) { - led_set_brightness(trigger_data->led_cdev, LED_OFF); - } - } else { - /* base state is OFF */ - /* ON -> OFF always */ - /* OFF -> ON on activity */ - if (trigger_data->led_cdev->brightness == LED_FULL) { - led_set_brightness(trigger_data->led_cdev, LED_OFF); - } else if (trigger_data->last_activity != new_activity) { - led_set_brightness(trigger_data->led_cdev, LED_FULL); - } - } - - trigger_data->last_activity = new_activity; - schedule_delayed_work(&trigger_data->work, trigger_data->interval); -} - -static void netdev_trig_activate(struct led_classdev *led_cdev) -{ - struct led_netdev_data *trigger_data; - int rc; - - trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); - if (!trigger_data) - return; - - spin_lock_init(&trigger_data->lock); - - trigger_data->notifier.notifier_call = netdev_trig_notify; - trigger_data->notifier.priority = 10; - - INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); - - trigger_data->led_cdev = led_cdev; - trigger_data->net_dev = NULL; - trigger_data->device_name[0] = 0; - - trigger_data->mode = 0; - trigger_data->interval = msecs_to_jiffies(50); - trigger_data->link_up = 0; - trigger_data->last_activity = 0; - - led_cdev->trigger_data = trigger_data; - - rc = device_create_file(led_cdev->dev, &dev_attr_device_name); - if (rc) - goto err_out; - rc = device_create_file(led_cdev->dev, &dev_attr_mode); - if (rc) - goto err_out_device_name; - rc = device_create_file(led_cdev->dev, &dev_attr_interval); - if (rc) - goto err_out_mode; - - register_netdevice_notifier(&trigger_data->notifier); - return; - -err_out_mode: - device_remove_file(led_cdev->dev, &dev_attr_mode); -err_out_device_name: - device_remove_file(led_cdev->dev, &dev_attr_device_name); -err_out: - led_cdev->trigger_data = NULL; - kfree(trigger_data); -} - -static void netdev_trig_deactivate(struct led_classdev *led_cdev) -{ - struct led_netdev_data *trigger_data = led_cdev->trigger_data; - - if (trigger_data) { - unregister_netdevice_notifier(&trigger_data->notifier); - - device_remove_file(led_cdev->dev, &dev_attr_device_name); - device_remove_file(led_cdev->dev, &dev_attr_mode); - device_remove_file(led_cdev->dev, &dev_attr_interval); - - cancel_delayed_work_sync(&trigger_data->work); - - spin_lock_bh(&trigger_data->lock); - - if (trigger_data->net_dev) { - dev_put(trigger_data->net_dev); - trigger_data->net_dev = NULL; - } - - spin_unlock_bh(&trigger_data->lock); - - kfree(trigger_data); - } -} - -static struct led_trigger netdev_led_trigger = { - .name = "netdev", - .activate = netdev_trig_activate, - .deactivate = netdev_trig_deactivate, -}; - -static int __init netdev_trig_init(void) -{ - return led_trigger_register(&netdev_led_trigger); -} - -static void __exit netdev_trig_exit(void) -{ - led_trigger_unregister(&netdev_led_trigger); -} - -module_init(netdev_trig_init); -module_exit(netdev_trig_exit); - -MODULE_AUTHOR("Oliver Jowett "); -MODULE_DESCRIPTION("Netdev LED trigger"); -MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/misc/owl-loader.c b/target/linux/generic/files/drivers/misc/owl-loader.c deleted file mode 100644 index f11cb2b49c..0000000000 --- a/target/linux/generic/files/drivers/misc/owl-loader.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Initialize Owl Emulation Devices - * - * Copyright (C) 2016 Christian Lamparter - * Copyright (C) 2016 Martin Blumenstingl - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * Some devices (like the Cisco Meraki Z1 Cloud Managed Teleworker Gateway) - * need to be able to initialize the PCIe wifi device. Normally, this is done - * during the early stages of booting linux, because the necessary init code - * is read from the memory mapped SPI and passed to pci_enable_ath9k_fixup. - * However,this isn't possible for devices which have the init code for the - * Atheros chip stored on NAND. Hence, this module can be used to initialze - * the chip when the user-space is ready to extract the init code. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct owl_ctx { - struct completion eeprom_load; -}; - -#define EEPROM_FILENAME_LEN 100 - -#define AR5416_EEPROM_MAGIC 0xa55a - -static int ath9k_pci_fixup(struct pci_dev *pdev, const u16 *cal_data, - size_t cal_len) -{ - void __iomem *mem; - const void *cal_end = (void *)cal_data + cal_len; - const struct { - __be16 reg; - __be16 low_val; - __be16 high_val; - } __packed *data; - u16 cmd; - u32 bar0; - bool swap_needed = false; - - if (*cal_data != AR5416_EEPROM_MAGIC) { - if (*cal_data != swab16(AR5416_EEPROM_MAGIC)) { - dev_err(&pdev->dev, "invalid calibration data\n"); - return -EINVAL; - } - - dev_dbg(&pdev->dev, "calibration data needs swapping\n"); - swap_needed = true; - } - - dev_info(&pdev->dev, "fixup device configuration\n"); - - mem = pcim_iomap(pdev, 0, 0); - if (!mem) { - dev_err(&pdev->dev, "ioremap error\n"); - return -EINVAL; - } - - pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0); - pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, - pci_resource_start(pdev, 0)); - pci_read_config_word(pdev, PCI_COMMAND, &cmd); - cmd |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; - pci_write_config_word(pdev, PCI_COMMAND, cmd); - - /* set pointer to first reg address */ - for (data = (const void *) (cal_data + 3); - (const void *) data <= cal_end && data->reg != cpu_to_be16(~0); - data++) { - u32 val; - u16 reg; - - reg = data->reg; - val = data->low_val; - val |= data->high_val << 16; - - if (swap_needed) { - reg = swab16(reg); - val = swahb32(val); - } - -#ifdef CONFIG_LANTIQ - val = swab32(val); -#endif - - __raw_writel(val, mem + reg); - udelay(100); - } - - pci_read_config_word(pdev, PCI_COMMAND, &cmd); - cmd &= ~(PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY); - pci_write_config_word(pdev, PCI_COMMAND, cmd); - - pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, bar0); - pcim_iounmap(pdev, mem); - - pci_disable_device(pdev); - - return 0; -} - -static void owl_fw_cb(const struct firmware *fw, void *context) -{ - struct pci_dev *pdev = (struct pci_dev *) context; - struct owl_ctx *ctx = (struct owl_ctx *) pci_get_drvdata(pdev); - struct ath9k_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct pci_bus *bus; - - complete(&ctx->eeprom_load); - - if (!fw) { - dev_err(&pdev->dev, "no eeprom data received.\n"); - goto release; - } - - /* also note that we are doing *u16 operations on the file */ - if (fw->size > sizeof(pdata->eeprom_data) || fw->size < 0x200 || - (fw->size & 1) == 1) { - dev_err(&pdev->dev, "eeprom file has an invalid size.\n"); - goto release; - } - - if (pdata) { - memcpy(pdata->eeprom_data, fw->data, fw->size); - - /* - * eeprom has been successfully loaded - pass the data to ath9k - * but remove the eeprom_name, so it doesn't try to load it too. - */ - pdata->eeprom_name = NULL; - } - - if (ath9k_pci_fixup(pdev, (const u16 *) fw->data, fw->size)) - goto release; - - pci_lock_rescan_remove(); - bus = pdev->bus; - pci_stop_and_remove_bus_device(pdev); - /* - * the device should come back with the proper - * ProductId. But we have to initiate a rescan. - */ - pci_rescan_bus(bus); - pci_unlock_rescan_remove(); - -release: - release_firmware(fw); -} - -static const char *owl_get_eeprom_name(struct pci_dev *pdev) -{ - struct device *dev = &pdev->dev; - struct ath9k_platform_data *pdata; - char *eeprom_name; - - /* try the existing platform data first */ - pdata = dev_get_platdata(dev); - if (pdata && pdata->eeprom_name) - return pdata->eeprom_name; - - dev_dbg(dev, "using auto-generated eeprom filename\n"); - - eeprom_name = devm_kzalloc(dev, EEPROM_FILENAME_LEN, GFP_KERNEL); - if (!eeprom_name) - return NULL; - - /* this should match the pattern used in ath9k/init.c */ - scnprintf(eeprom_name, EEPROM_FILENAME_LEN, "ath9k-eeprom-pci-%s.bin", - dev_name(dev)); - - return eeprom_name; -} - -static int owl_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - struct owl_ctx *ctx; - const char *eeprom_name; - int err = 0; - - if (pcim_enable_device(pdev)) - return -EIO; - - pcim_pin_device(pdev); - - eeprom_name = owl_get_eeprom_name(pdev); - if (!eeprom_name) { - dev_err(&pdev->dev, "no eeprom filename found.\n"); - return -ENODEV; - } - - ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); - if (!ctx) { - dev_err(&pdev->dev, "failed to alloc device context.\n"); - return -ENOMEM; - } - init_completion(&ctx->eeprom_load); - - pci_set_drvdata(pdev, ctx); - err = request_firmware_nowait(THIS_MODULE, true, eeprom_name, - &pdev->dev, GFP_KERNEL, pdev, owl_fw_cb); - if (err) { - dev_err(&pdev->dev, "failed to request caldata (%d).\n", err); - kfree(ctx); - } - return err; -} - -static void owl_remove(struct pci_dev *pdev) -{ - struct owl_ctx *ctx = pci_get_drvdata(pdev); - - if (ctx) { - wait_for_completion(&ctx->eeprom_load); - pci_set_drvdata(pdev, NULL); - kfree(ctx); - } -} - -static const struct pci_device_id owl_pci_table[] = { - { PCI_VDEVICE(ATHEROS, 0xff1c) }, /* PCIe */ - { PCI_VDEVICE(ATHEROS, 0xff1d) }, /* PCI */ - { }, -}; -MODULE_DEVICE_TABLE(pci, owl_pci_table); - -static struct pci_driver owl_driver = { - .name = "owl-loader", - .id_table = owl_pci_table, - .probe = owl_probe, - .remove = owl_remove, -}; -module_pci_driver(owl_driver); -MODULE_AUTHOR("Christian Lamparter "); -MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices"); -MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig b/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig deleted file mode 100644 index 81ece43db8..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig +++ /dev/null @@ -1,76 +0,0 @@ -config MTD_SPLIT - def_bool n - help - Generic MTD split support. - -config MTD_SPLIT_SUPPORT - def_bool MTD = y - -comment "Rootfs partition parsers" - -config MTD_SPLIT_SQUASHFS_ROOT - bool "Squashfs based root partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - default n - help - This provides a parsing function which allows to detect the - offset and size of the unused portion of a rootfs partition - containing a squashfs. - -comment "Firmware partition parsers" - -config MTD_SPLIT_SEAMA_FW - bool "Seama firmware parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_WRGG_FW - bool "WRGG firmware parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_UIMAGE_FW - bool "uImage based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_FIT_FW - bool "FIT based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_LZMA_FW - bool "LZMA compressed kernel based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_TPLINK_FW - bool "TP-Link firmware parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_TRX_FW - bool "TRX image based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_BRNIMAGE_FW - bool "brnImage (brnboot image) firmware parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_EVA_FW - bool "EVA image based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_MINOR_FW - bool "Mikrotik NOR image based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT - -config MTD_SPLIT_JIMAGE_FW - bool "JBOOT Image based firmware partition parser" - depends on MTD_SPLIT_SUPPORT - select MTD_SPLIT diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile b/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile deleted file mode 100644 index 206e754a18..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o -obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o -obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o -obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o -obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o -obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o -obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o -obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o -obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o -obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o -obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o -obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o -obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c deleted file mode 100644 index b2e51dcfc6..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2009-2013 Felix Fietkau - * Copyright (C) 2009-2013 Gabor Juhos - * Copyright (C) 2012 Jonas Gorski - * Copyright (C) 2013 Hauke Mehrtens - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#define pr_fmt(fmt) "mtdsplit: " fmt - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define UBI_EC_MAGIC 0x55424923 /* UBI# */ - -struct squashfs_super_block { - __le32 s_magic; - __le32 pad0[9]; - __le64 bytes_used; -}; - -int mtd_get_squashfs_len(struct mtd_info *master, - size_t offset, - size_t *squashfs_len) -{ - struct squashfs_super_block sb; - size_t retlen; - int err; - - err = mtd_read(master, offset, sizeof(sb), &retlen, (void *)&sb); - if (err || (retlen != sizeof(sb))) { - pr_alert("error occured while reading from \"%s\"\n", - master->name); - return -EIO; - } - - if (le32_to_cpu(sb.s_magic) != SQUASHFS_MAGIC) { - pr_alert("no squashfs found in \"%s\"\n", master->name); - return -EINVAL; - } - - retlen = le64_to_cpu(sb.bytes_used); - if (retlen <= 0) { - pr_alert("squashfs is empty in \"%s\"\n", master->name); - return -ENODEV; - } - - if (offset + retlen > master->size) { - pr_alert("squashfs has invalid size in \"%s\"\n", - master->name); - return -EINVAL; - } - - *squashfs_len = retlen; - return 0; -} -EXPORT_SYMBOL_GPL(mtd_get_squashfs_len); - -static ssize_t mtd_next_eb(struct mtd_info *mtd, size_t offset) -{ - return mtd_rounddown_to_eb(offset, mtd) + mtd->erasesize; -} - -int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, - enum mtdsplit_part_type *type) -{ - u32 magic; - size_t retlen; - int ret; - - ret = mtd_read(mtd, offset, sizeof(magic), &retlen, - (unsigned char *) &magic); - if (ret) - return ret; - - if (retlen != sizeof(magic)) - return -EIO; - - if (le32_to_cpu(magic) == SQUASHFS_MAGIC) { - if (type) - *type = MTDSPLIT_PART_TYPE_SQUASHFS; - return 0; - } else if (magic == 0x19852003) { - if (type) - *type = MTDSPLIT_PART_TYPE_JFFS2; - return 0; - } else if (be32_to_cpu(magic) == UBI_EC_MAGIC) { - if (type) - *type = MTDSPLIT_PART_TYPE_UBI; - return 0; - } - - return -EINVAL; -} -EXPORT_SYMBOL_GPL(mtd_check_rootfs_magic); - -int mtd_find_rootfs_from(struct mtd_info *mtd, - size_t from, - size_t limit, - size_t *ret_offset, - enum mtdsplit_part_type *type) -{ - size_t offset; - int err; - - for (offset = from; offset < limit; - offset = mtd_next_eb(mtd, offset)) { - err = mtd_check_rootfs_magic(mtd, offset, type); - if (err) - continue; - - *ret_offset = offset; - return 0; - } - - return -ENODEV; -} -EXPORT_SYMBOL_GPL(mtd_find_rootfs_from); - diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h deleted file mode 100644 index 71d62a8956..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2009-2013 Felix Fietkau - * Copyright (C) 2009-2013 Gabor Juhos - * Copyright (C) 2012 Jonas Gorski - * Copyright (C) 2013 Hauke Mehrtens - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#ifndef _MTDSPLIT_H -#define _MTDSPLIT_H - -#define KERNEL_PART_NAME "kernel" -#define ROOTFS_PART_NAME "rootfs" -#define UBI_PART_NAME "ubi" - -#define ROOTFS_SPLIT_NAME "rootfs_data" - -enum mtdsplit_part_type { - MTDSPLIT_PART_TYPE_UNK = 0, - MTDSPLIT_PART_TYPE_SQUASHFS, - MTDSPLIT_PART_TYPE_JFFS2, - MTDSPLIT_PART_TYPE_UBI, -}; - -#ifdef CONFIG_MTD_SPLIT -int mtd_get_squashfs_len(struct mtd_info *master, - size_t offset, - size_t *squashfs_len); - -int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, - enum mtdsplit_part_type *type); - -int mtd_find_rootfs_from(struct mtd_info *mtd, - size_t from, - size_t limit, - size_t *ret_offset, - enum mtdsplit_part_type *type); - -#else -static inline int mtd_get_squashfs_len(struct mtd_info *master, - size_t offset, - size_t *squashfs_len) -{ - return -ENODEV; -} - -static inline int mtd_check_rootfs_magic(struct mtd_info *mtd, size_t offset, - enum mtdsplit_part_type *type) -{ - return -EINVAL; -} - -static inline int mtd_find_rootfs_from(struct mtd_info *mtd, - size_t from, - size_t limit, - size_t *ret_offset, - enum mtdsplit_part_type *type) -{ - return -ENODEV; -} -#endif /* CONFIG_MTD_SPLIT */ - -#endif /* _MTDSPLIT_H */ diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_brnimage.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_brnimage.c deleted file mode 100644 index 3f2d79601a..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_brnimage.c +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2012 John Crispin - * Copyright (C) 2015 Martin Blumenstingl - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define BRNIMAGE_NR_PARTS 2 - -#define BRNIMAGE_ALIGN_BYTES 0x400 -#define BRNIMAGE_FOOTER_SIZE 12 - -#define BRNIMAGE_MIN_OVERHEAD (BRNIMAGE_FOOTER_SIZE) -#define BRNIMAGE_MAX_OVERHEAD (BRNIMAGE_ALIGN_BYTES + BRNIMAGE_FOOTER_SIZE) - -static int mtdsplit_parse_brnimage(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct mtd_partition *parts; - uint32_t buf; - unsigned long rootfs_offset, rootfs_size, kernel_size; - size_t len; - int ret = 0; - - for (rootfs_offset = 0; rootfs_offset < master->size; - rootfs_offset += BRNIMAGE_ALIGN_BYTES) { - ret = mtd_check_rootfs_magic(master, rootfs_offset, NULL); - if (!ret) - break; - } - - if (ret) - return ret; - - if (rootfs_offset >= master->size) - return -EINVAL; - - ret = mtd_read(master, rootfs_offset - BRNIMAGE_FOOTER_SIZE, 4, &len, - (void *)&buf); - if (ret) - return ret; - - if (len != 4) - return -EIO; - - kernel_size = le32_to_cpu(buf); - - if (kernel_size > (rootfs_offset - BRNIMAGE_MIN_OVERHEAD)) - return -EINVAL; - - if (kernel_size < (rootfs_offset - BRNIMAGE_MAX_OVERHEAD)) - return -EINVAL; - - /* - * The footer must be untouched as it contains the checksum of the - * original brnImage (kernel + squashfs)! - */ - rootfs_size = master->size - rootfs_offset - BRNIMAGE_FOOTER_SIZE; - - parts = kzalloc(BRNIMAGE_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = kernel_size; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = rootfs_size; - - *pparts = parts; - return BRNIMAGE_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_brnimage_parser = { - .owner = THIS_MODULE, - .name = "brnimage-fw", - .parse_fn = mtdsplit_parse_brnimage, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_brnimage_init(void) -{ - register_mtd_parser(&mtdsplit_brnimage_parser); - - return 0; -} - -subsys_initcall(mtdsplit_brnimage_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_eva.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_eva.c deleted file mode 100644 index 746944ee2e..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_eva.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2012 John Crispin - * Copyright (C) 2015 Martin Blumenstingl - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define EVA_NR_PARTS 2 -#define EVA_MAGIC 0xfeed1281 -#define EVA_FOOTER_SIZE 0x18 -#define EVA_DUMMY_SQUASHFS_SIZE 0x100 - -struct eva_image_header { - uint32_t magic; - uint32_t size; -}; - -static int mtdsplit_parse_eva(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct mtd_partition *parts; - struct eva_image_header hdr; - size_t retlen; - unsigned long kernel_size, rootfs_offset; - int err; - - err = mtd_read(master, 0, sizeof(hdr), &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != sizeof(hdr)) - return -EIO; - - if (le32_to_cpu(hdr.magic) != EVA_MAGIC) - return -EINVAL; - - kernel_size = le32_to_cpu(hdr.size) + EVA_FOOTER_SIZE; - - /* rootfs starts at the next 0x10000 boundary: */ - rootfs_offset = round_up(kernel_size, 0x10000); - - /* skip the dummy EVA squashfs partition (with wrong endianness): */ - rootfs_offset += EVA_DUMMY_SQUASHFS_SIZE; - - if (rootfs_offset >= master->size) - return -EINVAL; - - err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); - if (err) - return err; - - parts = kzalloc(EVA_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = kernel_size; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return EVA_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_eva_parser = { - .owner = THIS_MODULE, - .name = "eva-fw", - .parse_fn = mtdsplit_parse_eva, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_eva_init(void) -{ - register_mtd_parser(&mtdsplit_eva_parser); - - return 0; -} - -subsys_initcall(mtdsplit_eva_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c deleted file mode 100644 index f356adcd4e..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2015 The Linux Foundation - * Copyright (C) 2014 Gabor Juhos - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -struct fdt_header { - uint32_t magic; /* magic word FDT_MAGIC */ - uint32_t totalsize; /* total size of DT block */ - uint32_t off_dt_struct; /* offset to structure */ - uint32_t off_dt_strings; /* offset to strings */ - uint32_t off_mem_rsvmap; /* offset to memory reserve map */ - uint32_t version; /* format version */ - uint32_t last_comp_version; /* last compatible version */ - - /* version 2 fields below */ - uint32_t boot_cpuid_phys; /* Which physical CPU id we're - booting on */ - /* version 3 fields below */ - uint32_t size_dt_strings; /* size of the strings block */ - - /* version 17 fields below */ - uint32_t size_dt_struct; /* size of the structure block */ -}; - -static int -mtdsplit_fit_parse(struct mtd_info *mtd, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct fdt_header hdr; - size_t hdr_len, retlen; - size_t offset; - size_t fit_offset, fit_size; - size_t rootfs_offset, rootfs_size; - struct mtd_partition *parts; - int ret; - - hdr_len = sizeof(struct fdt_header); - - /* Parse the MTD device & search for the FIT image location */ - for(offset = 0; offset < mtd->size; offset += mtd->erasesize) { - ret = mtd_read(mtd, 0, hdr_len, &retlen, (void*) &hdr); - if (ret) { - pr_err("read error in \"%s\" at offset 0x%llx\n", - mtd->name, (unsigned long long) offset); - return ret; - } - - if (retlen != hdr_len) { - pr_err("short read in \"%s\"\n", mtd->name); - return -EIO; - } - - /* Check the magic - see if this is a FIT image */ - if (be32_to_cpu(hdr.magic) != OF_DT_HEADER) { - pr_debug("no valid FIT image found in \"%s\" at offset %llx\n", - mtd->name, (unsigned long long) offset); - continue; - } - - /* We found a FIT image. Let's keep going */ - break; - } - - fit_offset = offset; - fit_size = be32_to_cpu(hdr.totalsize); - - if (fit_size == 0) { - pr_err("FIT image in \"%s\" at offset %llx has null size\n", - mtd->name, (unsigned long long) fit_offset); - return -ENODEV; - } - - /* Search for the rootfs partition after the FIT image */ - ret = mtd_find_rootfs_from(mtd, fit_offset + fit_size, mtd->size, - &rootfs_offset, NULL); - if (ret) { - pr_info("no rootfs found after FIT image in \"%s\"\n", - mtd->name); - return ret; - } - - rootfs_size = mtd->size - rootfs_offset; - - parts = kzalloc(2 * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = fit_offset; - parts[0].size = mtd_rounddown_to_eb(fit_size, mtd) + mtd->erasesize; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = rootfs_size; - - *pparts = parts; - return 2; -} - -static struct mtd_part_parser uimage_parser = { - .owner = THIS_MODULE, - .name = "fit-fw", - .parse_fn = mtdsplit_fit_parse, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -/************************************************** - * Init - **************************************************/ - -static int __init mtdsplit_fit_init(void) -{ - register_mtd_parser(&uimage_parser); - - return 0; -} - -module_init(mtdsplit_fit_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_jimage.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_jimage.c deleted file mode 100644 index 51544a794b..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_jimage.c +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2018 Paweł Dembicki - * - * Based on: mtdsplit_uimage.c - * Copyright (C) 2013 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define MAX_HEADER_LEN ( STAG_SIZE + SCH2_SIZE ) - -#define STAG_SIZE 16 -#define STAG_ID 0x04 -#define STAG_MAGIC 0x2B24 - -#define SCH2_SIZE 40 -#define SCH2_MAGIC 0x2124 -#define SCH2_VER 0x02 - -/* - * Jboot image header, - * all data in little endian. - */ - -struct jimage_header //stag + sch2 jboot joined headers -{ - uint8_t stag_cmark; // in factory 0xFF , in sysupgrade must be the same as stag_id - uint8_t stag_id; // 0x04 - uint16_t stag_magic; //magic 0x2B24 - uint32_t stag_time_stamp; // timestamp calculated in jboot way - uint32_t stag_image_length; // lentgh of kernel + sch2 header - uint16_t stag_image_checksum; // negated jboot_checksum of sch2 + kernel - uint16_t stag_tag_checksum; // negated jboot_checksum of stag header data - uint16_t sch2_magic; // magic 0x2124 - uint8_t sch2_cp_type; // 0x00 for flat, 0x01 for jz, 0x02 for gzip, 0x03 for lzma - uint8_t sch2_version; // 0x02 for sch2 - uint32_t sch2_ram_addr; // ram entry address - uint32_t sch2_image_len; // kernel image length - uint32_t sch2_image_crc32; // kernel image crc - uint32_t sch2_start_addr; // ram start address - uint32_t sch2_rootfs_addr; // rootfs flash address - uint32_t sch2_rootfs_len; // rootfls length - uint32_t sch2_rootfs_crc32; // rootfs crc32 - uint32_t sch2_header_crc32; // sch2 header crc32, durring calculation this area is replaced by zero - uint16_t sch2_header_length; // sch2 header length: 0x28 - uint16_t sch2_cmd_line_length; // cmd line length, known zeros -}; - -static int -read_jimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, - size_t header_len) -{ - size_t retlen; - int ret; - - ret = mtd_read(mtd, offset, header_len, &retlen, buf); - if (ret) { - pr_debug("read error in \"%s\"\n", mtd->name); - return ret; - } - - if (retlen != header_len) { - pr_debug("short read in \"%s\"\n", mtd->name); - return -EIO; - } - - return 0; -} - -/** - * __mtdsplit_parse_jimage - scan partition and create kernel + rootfs parts - * - * @find_header: function to call for a block of data that will return offset - * of a valid jImage header if found - */ -static int __mtdsplit_parse_jimage(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data, - ssize_t (*find_header)(u_char *buf, size_t len)) -{ - struct mtd_partition *parts; - u_char *buf; - int nr_parts; - size_t offset; - size_t jimage_offset; - size_t jimage_size = 0; - size_t rootfs_offset; - size_t rootfs_size = 0; - int jimage_part, rf_part; - int ret; - enum mtdsplit_part_type type; - - nr_parts = 2; - parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - buf = vmalloc(MAX_HEADER_LEN); - if (!buf) { - ret = -ENOMEM; - goto err_free_parts; - } - - /* find jImage on erase block boundaries */ - for (offset = 0; offset < master->size; offset += master->erasesize) { - struct jimage_header *header; - - jimage_size = 0; - - ret = read_jimage_header(master, offset, buf, MAX_HEADER_LEN); - if (ret) - continue; - - ret = find_header(buf, MAX_HEADER_LEN); - if (ret < 0) { - pr_debug("no valid jImage found in \"%s\" at offset %llx\n", - master->name, (unsigned long long) offset); - continue; - } - header = (struct jimage_header *)(buf + ret); - - jimage_size = sizeof(*header) + header->sch2_image_len + ret; - if ((offset + jimage_size) > master->size) { - pr_debug("jImage exceeds MTD device \"%s\"\n", - master->name); - continue; - } - break; - } - - if (jimage_size == 0) { - pr_debug("no jImage found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err_free_buf; - } - - jimage_offset = offset; - - if (jimage_offset == 0) { - jimage_part = 0; - rf_part = 1; - - /* find the roots after the jImage */ - ret = mtd_find_rootfs_from(master, jimage_offset + jimage_size, - master->size, &rootfs_offset, &type); - if (ret) { - pr_debug("no rootfs after jImage in \"%s\"\n", - master->name); - goto err_free_buf; - } - - rootfs_size = master->size - rootfs_offset; - jimage_size = rootfs_offset - jimage_offset; - } else { - rf_part = 0; - jimage_part = 1; - - /* check rootfs presence at offset 0 */ - ret = mtd_check_rootfs_magic(master, 0, &type); - if (ret) { - pr_debug("no rootfs before jImage in \"%s\"\n", - master->name); - goto err_free_buf; - } - - rootfs_offset = 0; - rootfs_size = jimage_offset; - } - - if (rootfs_size == 0) { - pr_debug("no rootfs found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err_free_buf; - } - - parts[jimage_part].name = KERNEL_PART_NAME; - parts[jimage_part].offset = jimage_offset; - parts[jimage_part].size = jimage_size; - - if (type == MTDSPLIT_PART_TYPE_UBI) - parts[rf_part].name = UBI_PART_NAME; - else - parts[rf_part].name = ROOTFS_PART_NAME; - parts[rf_part].offset = rootfs_offset; - parts[rf_part].size = rootfs_size; - - vfree(buf); - - *pparts = parts; - return nr_parts; - -err_free_buf: - vfree(buf); - -err_free_parts: - kfree(parts); - return ret; -} - -static ssize_t jimage_verify_default(u_char *buf, size_t len) -{ - struct jimage_header *header = (struct jimage_header *)buf; - - /* default sanity checks */ - if (header->stag_magic != STAG_MAGIC) { - pr_debug("invalid jImage stag header magic: %04x\n", - header->stag_magic); - return -EINVAL; - } - if (header->sch2_magic != SCH2_MAGIC) { - pr_debug("invalid jImage sch2 header magic: %04x\n", - header->stag_magic); - return -EINVAL; - } - if (header->stag_cmark != header->stag_id) { - pr_debug("invalid jImage stag header cmark: %02x\n", - header->stag_magic); - return -EINVAL; - } - if (header->stag_id != STAG_ID) { - pr_debug("invalid jImage stag header id: %02x\n", - header->stag_magic); - return -EINVAL; - } - if (header->sch2_version != SCH2_VER) { - pr_debug("invalid jImage sch2 header version: %02x\n", - header->stag_magic); - return -EINVAL; - } - - return 0; -} - -static int -mtdsplit_jimage_parse_generic(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - return __mtdsplit_parse_jimage(master, pparts, data, - jimage_verify_default); -} - -static struct mtd_part_parser jimage_generic_parser = { - .owner = THIS_MODULE, - .name = "jimage-fw", - .parse_fn = mtdsplit_jimage_parse_generic, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -/************************************************** - * Init - **************************************************/ - -static int __init mtdsplit_jimage_init(void) -{ - register_mtd_parser(&jimage_generic_parser); - - return 0; -} - -module_init(mtdsplit_jimage_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c deleted file mode 100644 index b7f044a6dc..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2014 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include - -#include - -#include "mtdsplit.h" - -#define LZMA_NR_PARTS 2 -#define LZMA_PROPERTIES_SIZE 5 - -struct lzma_header { - u8 props[LZMA_PROPERTIES_SIZE]; - u8 size_low[4]; - u8 size_high[4]; -}; - -static int mtdsplit_parse_lzma(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct lzma_header hdr; - size_t hdr_len, retlen; - size_t rootfs_offset; - u32 t; - struct mtd_partition *parts; - int err; - - hdr_len = sizeof(hdr); - err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != hdr_len) - return -EIO; - - /* verify LZMA properties */ - if (hdr.props[0] >= (9 * 5 * 5)) - return -EINVAL; - - t = get_unaligned_le32(&hdr.props[1]); - if (!is_power_of_2(t)) - return -EINVAL; - - t = get_unaligned_le32(&hdr.size_high); - if (t) - return -EINVAL; - - err = mtd_find_rootfs_from(master, master->erasesize, master->size, - &rootfs_offset, NULL); - if (err) - return err; - - parts = kzalloc(LZMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = rootfs_offset; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return LZMA_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_lzma_parser = { - .owner = THIS_MODULE, - .name = "lzma-fw", - .parse_fn = mtdsplit_parse_lzma, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_lzma_init(void) -{ - register_mtd_parser(&mtdsplit_lzma_parser); - - return 0; -} - -subsys_initcall(mtdsplit_lzma_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_minor.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_minor.c deleted file mode 100644 index f971f0a650..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_minor.c +++ /dev/null @@ -1,117 +0,0 @@ -/* - * MTD splitter for MikroTik NOR devices - * - * Copyright (C) 2017 Thibaut VARENE - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * The rootfs is expected at erase-block boundary due to the use of - * mtd_find_rootfs_from(). We use a trimmed down version of the yaffs header - * for two main reasons: - * - the original header uses weakly defined types (int, enum...) which can - * vary in length depending on build host (and the struct is not packed), - * and the name field can have a different total length depending on - * whether or not the yaffs code was _built_ with unicode support. - * - the only field that could be of real use here (file_size_low) contains - * invalid data in the header generated by kernel2minor, so we cannot use - * it to infer the exact position of the rootfs and do away with - * mtd_find_rootfs_from() (and thus have non-EB-aligned rootfs). - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define YAFFS_OBJECT_TYPE_FILE 0x1 -#define YAFFS_OBJECTID_ROOT 0x1 -#define YAFFS_SUM_UNUSED 0xFFFF -#define YAFFS_NAME "kernel" - -#define MINOR_NR_PARTS 2 - -/* - * This structure is based on yaffs_obj_hdr from yaffs_guts.h - * The weak types match upstream. The fields have cpu-endianness - */ -struct minor_header { - int yaffs_type; - int yaffs_obj_id; - u16 yaffs_sum_unused; - char yaffs_name[sizeof(YAFFS_NAME)]; -}; - -static int mtdsplit_parse_minor(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct minor_header hdr; - size_t hdr_len, retlen; - size_t rootfs_offset; - struct mtd_partition *parts; - int err; - - hdr_len = sizeof(hdr); - err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != hdr_len) - return -EIO; - - /* match header */ - if (hdr.yaffs_type != YAFFS_OBJECT_TYPE_FILE) - return -EINVAL; - - if (hdr.yaffs_obj_id != YAFFS_OBJECTID_ROOT) - return -EINVAL; - - if (hdr.yaffs_sum_unused != YAFFS_SUM_UNUSED) - return -EINVAL; - - if (memcmp(hdr.yaffs_name, YAFFS_NAME, sizeof(YAFFS_NAME))) - return -EINVAL; - - err = mtd_find_rootfs_from(master, master->erasesize, master->size, - &rootfs_offset, NULL); - if (err) - return err; - - parts = kzalloc(MINOR_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = rootfs_offset; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return MINOR_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_minor_parser = { - .owner = THIS_MODULE, - .name = "minor-fw", - .parse_fn = mtdsplit_parse_minor, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_minor_init(void) -{ - register_mtd_parser(&mtdsplit_minor_parser); - - return 0; -} - -subsys_initcall(mtdsplit_minor_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c deleted file mode 100644 index f8556e05a4..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2013 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define SEAMA_MAGIC 0x5EA3A417 -#define SEAMA_NR_PARTS 2 -#define SEAMA_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ - -struct seama_header { - __be32 magic; /* should always be SEAMA_MAGIC. */ - __be16 reserved; /* reserved for */ - __be16 metasize; /* size of the META data */ - __be32 size; /* size of the image */ - u8 md5[16]; /* digest */ -}; - -static int mtdsplit_parse_seama(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct seama_header hdr; - size_t hdr_len, retlen, kernel_ent_size; - size_t rootfs_offset; - struct mtd_partition *parts; - enum mtdsplit_part_type type; - int err; - - hdr_len = sizeof(hdr); - err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != hdr_len) - return -EIO; - - /* sanity checks */ - if (be32_to_cpu(hdr.magic) != SEAMA_MAGIC) - return -EINVAL; - - kernel_ent_size = hdr_len + be32_to_cpu(hdr.size) + - be16_to_cpu(hdr.metasize); - if (kernel_ent_size > master->size) - return -EINVAL; - - /* Check for the rootfs right after Seama entity with a kernel. */ - err = mtd_check_rootfs_magic(master, kernel_ent_size, &type); - if (!err) { - rootfs_offset = kernel_ent_size; - } else { - /* - * On some devices firmware entity might contain both: kernel - * and rootfs. We can't determine kernel size so we just have to - * look for rootfs magic. - * Start the search from an arbitrary offset. - */ - err = mtd_find_rootfs_from(master, SEAMA_MIN_ROOTFS_OFFS, - master->size, &rootfs_offset, &type); - if (err) - return err; - } - - parts = kzalloc(SEAMA_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = sizeof hdr + be16_to_cpu(hdr.metasize); - parts[0].size = rootfs_offset - parts[0].offset; - - if (type == MTDSPLIT_PART_TYPE_UBI) - parts[1].name = UBI_PART_NAME; - else - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return SEAMA_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_seama_parser = { - .owner = THIS_MODULE, - .name = "seama-fw", - .parse_fn = mtdsplit_parse_seama, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_seama_init(void) -{ - register_mtd_parser(&mtdsplit_seama_parser); - - return 0; -} - -subsys_initcall(mtdsplit_seama_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c deleted file mode 100644 index 79e1f73bca..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 Felix Fietkau - * Copyright (C) 2013 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -static int -mtdsplit_parse_squashfs(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct mtd_partition *part; - struct mtd_info *parent_mtd; - size_t part_offset; - size_t squashfs_len; - int err; - - err = mtd_get_squashfs_len(master, 0, &squashfs_len); - if (err) - return err; - - parent_mtd = mtdpart_get_master(master); - part_offset = mtdpart_get_offset(master); - - part = kzalloc(sizeof(*part), GFP_KERNEL); - if (!part) { - pr_alert("unable to allocate memory for \"%s\" partition\n", - ROOTFS_SPLIT_NAME); - return -ENOMEM; - } - - part->name = ROOTFS_SPLIT_NAME; - part->offset = mtd_roundup_to_eb(part_offset + squashfs_len, - parent_mtd) - part_offset; - part->size = mtd_rounddown_to_eb(master->size - part->offset, master); - - *pparts = part; - return 1; -} - -static struct mtd_part_parser mtdsplit_squashfs_parser = { - .owner = THIS_MODULE, - .name = "squashfs-split", - .parse_fn = mtdsplit_parse_squashfs, - .type = MTD_PARSER_TYPE_ROOTFS, -}; - -static int __init mtdsplit_squashfs_init(void) -{ - register_mtd_parser(&mtdsplit_squashfs_parser); - - return 0; -} - -subsys_initcall(mtdsplit_squashfs_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c deleted file mode 100644 index c346aa8130..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2013 Gabor Juhos - * Copyright (C) 2014 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define TPLINK_NR_PARTS 2 -#define TPLINK_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ - -#define MD5SUM_LEN 16 - -struct fw_v1 { - char vendor_name[24]; - char fw_version[36]; - uint32_t hw_id; /* hardware id */ - uint32_t hw_rev; /* hardware revision */ - uint32_t unk1; - uint8_t md5sum1[MD5SUM_LEN]; - uint32_t unk2; - uint8_t md5sum2[MD5SUM_LEN]; - uint32_t unk3; - uint32_t kernel_la; /* kernel load address */ - uint32_t kernel_ep; /* kernel entry point */ - uint32_t fw_length; /* total length of the firmware */ - uint32_t kernel_ofs; /* kernel data offset */ - uint32_t kernel_len; /* kernel data length */ - uint32_t rootfs_ofs; /* rootfs data offset */ - uint32_t rootfs_len; /* rootfs data length */ - uint32_t boot_ofs; /* bootloader data offset */ - uint32_t boot_len; /* bootloader data length */ - uint8_t pad[360]; -} __attribute__ ((packed)); - -struct fw_v2 { - char fw_version[48]; /* 0x04: fw version string */ - uint32_t hw_id; /* 0x34: hardware id */ - uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */ - uint32_t unk1; /* 0x3c: 0x00000000 */ - uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */ - uint32_t unk2; /* 0x50: 0x00000000 */ - uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */ - uint32_t unk3; /* 0x64: 0xffffffff */ - - uint32_t kernel_la; /* 0x68: kernel load address */ - uint32_t kernel_ep; /* 0x6c: kernel entry point */ - uint32_t fw_length; /* 0x70: total length of the image */ - uint32_t kernel_ofs; /* 0x74: kernel data offset */ - uint32_t kernel_len; /* 0x78: kernel data length */ - uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */ - uint32_t rootfs_len; /* 0x80: rootfs data length */ - uint32_t boot_ofs; /* 0x84: FIXME: seems to be unused */ - uint32_t boot_len; /* 0x88: FIXME: seems to be unused */ - uint16_t unk4; /* 0x8c: 0x55aa */ - uint8_t sver_hi; /* 0x8e */ - uint8_t sver_lo; /* 0x8f */ - uint8_t unk5; /* 0x90: magic: 0xa5 */ - uint8_t ver_hi; /* 0x91 */ - uint8_t ver_mid; /* 0x92 */ - uint8_t ver_lo; /* 0x93 */ - uint8_t pad[364]; -} __attribute__ ((packed)); - -struct tplink_fw_header { - uint32_t version; - union { - struct fw_v1 v1; - struct fw_v2 v2; - }; -}; - -static int mtdsplit_parse_tplink(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct tplink_fw_header hdr; - size_t hdr_len, retlen, kernel_size; - size_t rootfs_offset; - struct mtd_partition *parts; - int err; - - hdr_len = sizeof(hdr); - err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != hdr_len) - return -EIO; - - switch (le32_to_cpu(hdr.version)) { - case 1: - if (be32_to_cpu(hdr.v1.kernel_ofs) != sizeof(hdr)) - return -EINVAL; - - kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v1.kernel_len); - rootfs_offset = be32_to_cpu(hdr.v1.rootfs_ofs); - break; - case 2: - case 3: - if (be32_to_cpu(hdr.v2.kernel_ofs) != sizeof(hdr)) - return -EINVAL; - - kernel_size = sizeof(hdr) + be32_to_cpu(hdr.v2.kernel_len); - rootfs_offset = be32_to_cpu(hdr.v2.rootfs_ofs); - break; - default: - return -EINVAL; - } - - if (kernel_size > master->size) - return -EINVAL; - - /* Find the rootfs */ - err = mtd_check_rootfs_magic(master, rootfs_offset, NULL); - if (err) { - /* - * The size in the header might cover the rootfs as well. - * Start the search from an arbitrary offset. - */ - err = mtd_find_rootfs_from(master, TPLINK_MIN_ROOTFS_OFFS, - master->size, &rootfs_offset, NULL); - if (err) - return err; - } - - parts = kzalloc(TPLINK_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = kernel_size; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return TPLINK_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_tplink_parser = { - .owner = THIS_MODULE, - .name = "tplink-fw", - .parse_fn = mtdsplit_parse_tplink, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_tplink_init(void) -{ - register_mtd_parser(&mtdsplit_tplink_parser); - - return 0; -} - -subsys_initcall(mtdsplit_tplink_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c deleted file mode 100644 index 53aebc5987..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2013 Gabor Juhos - * Copyright (C) 2014 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define TRX_MAGIC 0x30524448 /* "HDR0" */ - -struct trx_header { - __le32 magic; - __le32 len; - __le32 crc32; - __le32 flag_version; - __le32 offset[4]; -}; - -static int -read_trx_header(struct mtd_info *mtd, size_t offset, - struct trx_header *header) -{ - size_t header_len; - size_t retlen; - int ret; - - header_len = sizeof(*header); - ret = mtd_read(mtd, offset, header_len, &retlen, - (unsigned char *) header); - if (ret) { - pr_debug("read error in \"%s\"\n", mtd->name); - return ret; - } - - if (retlen != header_len) { - pr_debug("short read in \"%s\"\n", mtd->name); - return -EIO; - } - - return 0; -} - -static int -mtdsplit_parse_trx(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct mtd_partition *parts; - struct trx_header hdr; - int nr_parts; - size_t offset; - size_t trx_offset; - size_t trx_size = 0; - size_t rootfs_offset; - size_t rootfs_size = 0; - int ret; - - nr_parts = 2; - parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - /* find trx image on erase block boundaries */ - for (offset = 0; offset < master->size; offset += master->erasesize) { - trx_size = 0; - - ret = read_trx_header(master, offset, &hdr); - if (ret) - continue; - - if (hdr.magic != cpu_to_le32(TRX_MAGIC)) { - pr_debug("no valid trx header found in \"%s\" at offset %llx\n", - master->name, (unsigned long long) offset); - continue; - } - - trx_size = le32_to_cpu(hdr.len); - if ((offset + trx_size) > master->size) { - pr_debug("trx image exceeds MTD device \"%s\"\n", - master->name); - continue; - } - break; - } - - if (trx_size == 0) { - pr_debug("no trx header found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err; - } - - trx_offset = offset + hdr.offset[0]; - rootfs_offset = offset + hdr.offset[1]; - rootfs_size = master->size - rootfs_offset; - trx_size = rootfs_offset - trx_offset; - - if (rootfs_size == 0) { - pr_debug("no rootfs found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err; - } - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = trx_offset; - parts[0].size = trx_size; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = rootfs_size; - - *pparts = parts; - return nr_parts; - -err: - kfree(parts); - return ret; -} - -static struct mtd_part_parser trx_parser = { - .owner = THIS_MODULE, - .name = "trx-fw", - .parse_fn = mtdsplit_parse_trx, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_trx_init(void) -{ - register_mtd_parser(&trx_parser); - - return 0; -} - -module_init(mtdsplit_trx_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c deleted file mode 100644 index bd1c723e74..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2013 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -/* - * uimage_header itself is only 64B, but it may be prepended with another data. - * Currently the biggest size is for Edimax devices: 20B + 64B - */ -#define MAX_HEADER_LEN 84 - -#define IH_MAGIC 0x27051956 /* Image Magic Number */ -#define IH_NMLEN 32 /* Image Name Length */ - -#define IH_OS_LINUX 5 /* Linux */ - -#define IH_TYPE_KERNEL 2 /* OS Kernel Image */ -#define IH_TYPE_FILESYSTEM 7 /* Filesystem Image */ - -/* - * Legacy format image header, - * all data in network byte order (aka natural aka bigendian). - */ -struct uimage_header { - uint32_t ih_magic; /* Image Header Magic Number */ - uint32_t ih_hcrc; /* Image Header CRC Checksum */ - uint32_t ih_time; /* Image Creation Timestamp */ - uint32_t ih_size; /* Image Data Size */ - uint32_t ih_load; /* Data Load Address */ - uint32_t ih_ep; /* Entry Point Address */ - uint32_t ih_dcrc; /* Image Data CRC Checksum */ - uint8_t ih_os; /* Operating System */ - uint8_t ih_arch; /* CPU architecture */ - uint8_t ih_type; /* Image Type */ - uint8_t ih_comp; /* Compression Type */ - uint8_t ih_name[IH_NMLEN]; /* Image Name */ -}; - -static int -read_uimage_header(struct mtd_info *mtd, size_t offset, u_char *buf, - size_t header_len) -{ - size_t retlen; - int ret; - - ret = mtd_read(mtd, offset, header_len, &retlen, buf); - if (ret) { - pr_debug("read error in \"%s\"\n", mtd->name); - return ret; - } - - if (retlen != header_len) { - pr_debug("short read in \"%s\"\n", mtd->name); - return -EIO; - } - - return 0; -} - -/** - * __mtdsplit_parse_uimage - scan partition and create kernel + rootfs parts - * - * @find_header: function to call for a block of data that will return offset - * of a valid uImage header if found - */ -static int __mtdsplit_parse_uimage(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data, - ssize_t (*find_header)(u_char *buf, size_t len)) -{ - struct mtd_partition *parts; - u_char *buf; - int nr_parts; - size_t offset; - size_t uimage_offset; - size_t uimage_size = 0; - size_t rootfs_offset; - size_t rootfs_size = 0; - int uimage_part, rf_part; - int ret; - enum mtdsplit_part_type type; - - nr_parts = 2; - parts = kzalloc(nr_parts * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - buf = vmalloc(MAX_HEADER_LEN); - if (!buf) { - ret = -ENOMEM; - goto err_free_parts; - } - - /* find uImage on erase block boundaries */ - for (offset = 0; offset < master->size; offset += master->erasesize) { - struct uimage_header *header; - - uimage_size = 0; - - ret = read_uimage_header(master, offset, buf, MAX_HEADER_LEN); - if (ret) - continue; - - ret = find_header(buf, MAX_HEADER_LEN); - if (ret < 0) { - pr_debug("no valid uImage found in \"%s\" at offset %llx\n", - master->name, (unsigned long long) offset); - continue; - } - header = (struct uimage_header *)(buf + ret); - - uimage_size = sizeof(*header) + be32_to_cpu(header->ih_size) + ret; - if ((offset + uimage_size) > master->size) { - pr_debug("uImage exceeds MTD device \"%s\"\n", - master->name); - continue; - } - break; - } - - if (uimage_size == 0) { - pr_debug("no uImage found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err_free_buf; - } - - uimage_offset = offset; - - if (uimage_offset == 0) { - uimage_part = 0; - rf_part = 1; - - /* find the roots after the uImage */ - ret = mtd_find_rootfs_from(master, uimage_offset + uimage_size, - master->size, &rootfs_offset, &type); - if (ret) { - pr_debug("no rootfs after uImage in \"%s\"\n", - master->name); - goto err_free_buf; - } - - rootfs_size = master->size - rootfs_offset; - uimage_size = rootfs_offset - uimage_offset; - } else { - rf_part = 0; - uimage_part = 1; - - /* check rootfs presence at offset 0 */ - ret = mtd_check_rootfs_magic(master, 0, &type); - if (ret) { - pr_debug("no rootfs before uImage in \"%s\"\n", - master->name); - goto err_free_buf; - } - - rootfs_offset = 0; - rootfs_size = uimage_offset; - } - - if (rootfs_size == 0) { - pr_debug("no rootfs found in \"%s\"\n", master->name); - ret = -ENODEV; - goto err_free_buf; - } - - parts[uimage_part].name = KERNEL_PART_NAME; - parts[uimage_part].offset = uimage_offset; - parts[uimage_part].size = uimage_size; - - if (type == MTDSPLIT_PART_TYPE_UBI) - parts[rf_part].name = UBI_PART_NAME; - else - parts[rf_part].name = ROOTFS_PART_NAME; - parts[rf_part].offset = rootfs_offset; - parts[rf_part].size = rootfs_size; - - vfree(buf); - - *pparts = parts; - return nr_parts; - -err_free_buf: - vfree(buf); - -err_free_parts: - kfree(parts); - return ret; -} - -static ssize_t uimage_verify_default(u_char *buf, size_t len) -{ - struct uimage_header *header = (struct uimage_header *)buf; - - /* default sanity checks */ - if (be32_to_cpu(header->ih_magic) != IH_MAGIC) { - pr_debug("invalid uImage magic: %08x\n", - be32_to_cpu(header->ih_magic)); - return -EINVAL; - } - - if (header->ih_os != IH_OS_LINUX) { - pr_debug("invalid uImage OS: %08x\n", - be32_to_cpu(header->ih_os)); - return -EINVAL; - } - - if (header->ih_type != IH_TYPE_KERNEL) { - pr_debug("invalid uImage type: %08x\n", - be32_to_cpu(header->ih_type)); - return -EINVAL; - } - - return 0; -} - -static int -mtdsplit_uimage_parse_generic(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - return __mtdsplit_parse_uimage(master, pparts, data, - uimage_verify_default); -} - -static struct mtd_part_parser uimage_generic_parser = { - .owner = THIS_MODULE, - .name = "uimage-fw", - .parse_fn = mtdsplit_uimage_parse_generic, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -#define FW_MAGIC_WNR2000V1 0x32303031 -#define FW_MAGIC_WNR2000V3 0x32303033 -#define FW_MAGIC_WNR2000V4 0x32303034 -#define FW_MAGIC_WNR2200 0x32323030 -#define FW_MAGIC_WNR612V2 0x32303631 -#define FW_MAGIC_WNR1000V2 0x31303031 -#define FW_MAGIC_WNR1000V2_VC 0x31303030 -#define FW_MAGIC_WNDR3700 0x33373030 -#define FW_MAGIC_WNDR3700V2 0x33373031 -#define FW_MAGIC_WPN824N 0x31313030 - -static ssize_t uimage_verify_wndr3700(u_char *buf, size_t len) -{ - struct uimage_header *header = (struct uimage_header *)buf; - uint8_t expected_type = IH_TYPE_FILESYSTEM; - - switch (be32_to_cpu(header->ih_magic)) { - case FW_MAGIC_WNR612V2: - case FW_MAGIC_WNR1000V2: - case FW_MAGIC_WNR1000V2_VC: - case FW_MAGIC_WNR2000V1: - case FW_MAGIC_WNR2000V3: - case FW_MAGIC_WNR2200: - case FW_MAGIC_WNDR3700: - case FW_MAGIC_WNDR3700V2: - case FW_MAGIC_WPN824N: - break; - case FW_MAGIC_WNR2000V4: - expected_type = IH_TYPE_KERNEL; - break; - default: - return -EINVAL; - } - - if (header->ih_os != IH_OS_LINUX || - header->ih_type != expected_type) - return -EINVAL; - - return 0; -} - -static int -mtdsplit_uimage_parse_netgear(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - return __mtdsplit_parse_uimage(master, pparts, data, - uimage_verify_wndr3700); -} - -static struct mtd_part_parser uimage_netgear_parser = { - .owner = THIS_MODULE, - .name = "netgear-fw", - .parse_fn = mtdsplit_uimage_parse_netgear, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -/************************************************** - * Edimax - **************************************************/ - -#define FW_EDIMAX_OFFSET 20 -#define FW_MAGIC_EDIMAX 0x43535953 - -static ssize_t uimage_find_edimax(u_char *buf, size_t len) -{ - u32 *magic; - - if (len < FW_EDIMAX_OFFSET + sizeof(struct uimage_header)) { - pr_err("Buffer too small for checking Edimax header\n"); - return -ENOSPC; - } - - magic = (u32 *)buf; - if (be32_to_cpu(*magic) != FW_MAGIC_EDIMAX) - return -EINVAL; - - if (!uimage_verify_default(buf + FW_EDIMAX_OFFSET, len)) - return FW_EDIMAX_OFFSET; - - return -EINVAL; -} - -static int -mtdsplit_uimage_parse_edimax(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - return __mtdsplit_parse_uimage(master, pparts, data, - uimage_find_edimax); -} - -static struct mtd_part_parser uimage_edimax_parser = { - .owner = THIS_MODULE, - .name = "edimax-fw", - .parse_fn = mtdsplit_uimage_parse_edimax, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -/************************************************** - * Init - **************************************************/ - -static int __init mtdsplit_uimage_init(void) -{ - register_mtd_parser(&uimage_generic_parser); - register_mtd_parser(&uimage_netgear_parser); - register_mtd_parser(&uimage_edimax_parser); - - return 0; -} - -module_init(mtdsplit_uimage_init); diff --git a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_wrgg.c b/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_wrgg.c deleted file mode 100644 index 16ebd51345..0000000000 --- a/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_wrgg.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2013 Gabor Juhos - * Copyright (C) 2014 Felix Fietkau - * Copyright (C) 2016 Stijn Tintel - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mtdsplit.h" - -#define WRGG_NR_PARTS 2 -#define WRGG_MIN_ROOTFS_OFFS 0x80000 /* 512KiB */ -#define WRGG03_MAGIC 0x20080321 -#define WRG_MAGIC 0x20040220 - -struct wrgg03_header { - char signature[32]; - uint32_t magic1; - uint32_t magic2; - char version[16]; - char model[16]; - uint32_t flag[2]; - uint32_t reserve[2]; - char buildno[16]; - uint32_t size; - uint32_t offset; - char devname[32]; - char digest[16]; -} __attribute__ ((packed)); - -struct wrg_header { - char signature[32]; - uint32_t magic1; - uint32_t magic2; - uint32_t size; - uint32_t offset; - char devname[32]; - char digest[16]; -} __attribute__ ((packed)); - - -static int mtdsplit_parse_wrgg(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct wrgg03_header hdr; - size_t hdr_len, retlen, kernel_ent_size; - size_t rootfs_offset; - struct mtd_partition *parts; - enum mtdsplit_part_type type; - int err; - - hdr_len = sizeof(hdr); - err = mtd_read(master, 0, hdr_len, &retlen, (void *) &hdr); - if (err) - return err; - - if (retlen != hdr_len) - return -EIO; - - /* sanity checks */ - if (le32_to_cpu(hdr.magic1) == WRGG03_MAGIC) { - kernel_ent_size = hdr_len + be32_to_cpu(hdr.size); - } else if (le32_to_cpu(hdr.magic1) == WRG_MAGIC) { - kernel_ent_size = sizeof(struct wrg_header) + le32_to_cpu( - ((struct wrg_header*)&hdr)->size); - } else { - return -EINVAL; - } - - if (kernel_ent_size > master->size) - return -EINVAL; - - /* - * The size in the header covers the rootfs as well. - * Start the search from an arbitrary offset. - */ - err = mtd_find_rootfs_from(master, WRGG_MIN_ROOTFS_OFFS, - master->size, &rootfs_offset, &type); - if (err) - return err; - - parts = kzalloc(WRGG_NR_PARTS * sizeof(*parts), GFP_KERNEL); - if (!parts) - return -ENOMEM; - - parts[0].name = KERNEL_PART_NAME; - parts[0].offset = 0; - parts[0].size = rootfs_offset; - - parts[1].name = ROOTFS_PART_NAME; - parts[1].offset = rootfs_offset; - parts[1].size = master->size - rootfs_offset; - - *pparts = parts; - return WRGG_NR_PARTS; -} - -static struct mtd_part_parser mtdsplit_wrgg_parser = { - .owner = THIS_MODULE, - .name = "wrgg-fw", - .parse_fn = mtdsplit_parse_wrgg, - .type = MTD_PARSER_TYPE_FIRMWARE, -}; - -static int __init mtdsplit_wrgg_init(void) -{ - register_mtd_parser(&mtdsplit_wrgg_parser); - - return 0; -} - -subsys_initcall(mtdsplit_wrgg_init); diff --git a/target/linux/generic/files/drivers/mtd/myloader.c b/target/linux/generic/files/drivers/mtd/myloader.c deleted file mode 100644 index 7532d454ed..0000000000 --- a/target/linux/generic/files/drivers/mtd/myloader.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Parse MyLoader-style flash partition tables and produce a Linux partition - * array to match. - * - * Copyright (C) 2007-2009 Gabor Juhos - * - * This file was based on drivers/mtd/redboot.c - * Author: Red Hat, Inc. - David Woodhouse - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define BLOCK_LEN_MIN 0x10000 -#define PART_NAME_LEN 32 - -struct part_data { - struct mylo_partition_table tab; - char names[MYLO_MAX_PARTITIONS][PART_NAME_LEN]; -}; - -static int myloader_parse_partitions(struct mtd_info *master, - const struct mtd_partition **pparts, - struct mtd_part_parser_data *data) -{ - struct part_data *buf; - struct mylo_partition_table *tab; - struct mylo_partition *part; - struct mtd_partition *mtd_parts; - struct mtd_partition *mtd_part; - int num_parts; - int ret, i; - size_t retlen; - char *names; - unsigned long offset; - unsigned long blocklen; - - buf = vmalloc(sizeof(*buf)); - if (!buf) { - return -ENOMEM; - goto out; - } - tab = &buf->tab; - - blocklen = master->erasesize; - if (blocklen < BLOCK_LEN_MIN) - blocklen = BLOCK_LEN_MIN; - - offset = blocklen; - - /* Find the partition table */ - for (i = 0; i < 4; i++, offset += blocklen) { - printk(KERN_DEBUG "%s: searching for MyLoader partition table" - " at offset 0x%lx\n", master->name, offset); - - ret = mtd_read(master, offset, sizeof(*buf), &retlen, - (void *)buf); - if (ret) - goto out_free_buf; - - if (retlen != sizeof(*buf)) { - ret = -EIO; - goto out_free_buf; - } - - /* Check for Partition Table magic number */ - if (tab->magic == le32_to_cpu(MYLO_MAGIC_PARTITIONS)) - break; - - } - - if (tab->magic != le32_to_cpu(MYLO_MAGIC_PARTITIONS)) { - printk(KERN_DEBUG "%s: no MyLoader partition table found\n", - master->name); - ret = 0; - goto out_free_buf; - } - - /* The MyLoader and the Partition Table is always present */ - num_parts = 2; - - /* Detect number of used partitions */ - for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { - part = &tab->partitions[i]; - - if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) - continue; - - num_parts++; - } - - mtd_parts = kzalloc((num_parts * sizeof(*mtd_part) + - num_parts * PART_NAME_LEN), GFP_KERNEL); - - if (!mtd_parts) { - ret = -ENOMEM; - goto out_free_buf; - } - - mtd_part = mtd_parts; - names = (char *)&mtd_parts[num_parts]; - - strncpy(names, "myloader", PART_NAME_LEN); - mtd_part->name = names; - mtd_part->offset = 0; - mtd_part->size = offset; - mtd_part->mask_flags = MTD_WRITEABLE; - mtd_part++; - names += PART_NAME_LEN; - - strncpy(names, "partition_table", PART_NAME_LEN); - mtd_part->name = names; - mtd_part->offset = offset; - mtd_part->size = blocklen; - mtd_part->mask_flags = MTD_WRITEABLE; - mtd_part++; - names += PART_NAME_LEN; - - for (i = 0; i < MYLO_MAX_PARTITIONS; i++) { - part = &tab->partitions[i]; - - if (le16_to_cpu(part->type) == PARTITION_TYPE_FREE) - continue; - - if ((buf->names[i][0]) && (buf->names[i][0] != '\xff')) - strncpy(names, buf->names[i], PART_NAME_LEN); - else - snprintf(names, PART_NAME_LEN, "partition%d", i); - - mtd_part->offset = le32_to_cpu(part->addr); - mtd_part->size = le32_to_cpu(part->size); - mtd_part->name = names; - mtd_part++; - names += PART_NAME_LEN; - } - - *pparts = mtd_parts; - ret = num_parts; - - out_free_buf: - vfree(buf); - out: - return ret; -} - -static struct mtd_part_parser myloader_mtd_parser = { - .owner = THIS_MODULE, - .parse_fn = myloader_parse_partitions, - .name = "MyLoader", -}; - -static int __init myloader_mtd_parser_init(void) -{ - register_mtd_parser(&myloader_mtd_parser); - - return 0; -} - -static void __exit myloader_mtd_parser_exit(void) -{ - deregister_mtd_parser(&myloader_mtd_parser); -} - -module_init(myloader_mtd_parser_init); -module_exit(myloader_mtd_parser_exit); - -MODULE_AUTHOR("Gabor Juhos "); -MODULE_DESCRIPTION("Parsing code for MyLoader partition tables"); -MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.c b/target/linux/generic/files/drivers/net/phy/adm6996.c deleted file mode 100644 index 42928bab07..0000000000 --- a/target/linux/generic/files/drivers/net/phy/adm6996.c +++ /dev/null @@ -1,1241 +0,0 @@ -/* - * ADM6996 switch driver - * - * swconfig interface based on ar8216.c - * - * Copyright (c) 2008 Felix Fietkau - * VLAN support Copyright (c) 2010, 2011 Peter Lebbing - * Copyright (c) 2013 Hauke Mehrtens - * Copyright (c) 2014 Matti Laakso - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -/*#define DEBUG 1*/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "adm6996.h" - -MODULE_DESCRIPTION("Infineon ADM6996 Switch"); -MODULE_AUTHOR("Felix Fietkau, Peter Lebbing "); -MODULE_LICENSE("GPL"); - -static const char * const adm6996_model_name[] = -{ - NULL, - "ADM6996FC", - "ADM6996M", - "ADM6996L" -}; - -struct adm6996_mib_desc { - unsigned int offset; - const char *name; -}; - -struct adm6996_priv { - struct switch_dev dev; - void *priv; - - u8 eecs; - u8 eesk; - u8 eedi; - - enum adm6996_model model; - - bool enable_vlan; - bool vlan_enabled; /* Current hardware state */ - -#ifdef DEBUG - u16 addr; /* Debugging: register address to operate on */ -#endif - - u16 pvid[ADM_NUM_PORTS]; /* Primary VLAN ID */ - u8 tagged_ports; - - u16 vlan_id[ADM_NUM_VLANS]; - u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ - u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ - - struct mutex mib_lock; - char buf[2048]; - - struct mutex reg_mutex; - - /* use abstraction for regops, we want to add gpio support in the future */ - u16 (*read)(struct adm6996_priv *priv, enum admreg reg); - void (*write)(struct adm6996_priv *priv, enum admreg reg, u16 val); -}; - -#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) -#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) - -#define MIB_DESC(_o, _n) \ - { \ - .offset = (_o), \ - .name = (_n), \ - } - -static const struct adm6996_mib_desc adm6996_mibs[] = { - MIB_DESC(ADM_CL0, "RxPacket"), - MIB_DESC(ADM_CL6, "RxByte"), - MIB_DESC(ADM_CL12, "TxPacket"), - MIB_DESC(ADM_CL18, "TxByte"), - MIB_DESC(ADM_CL24, "Collision"), - MIB_DESC(ADM_CL30, "Error"), -}; - -#define ADM6996_MIB_RXB_ID 1 -#define ADM6996_MIB_TXB_ID 3 - -static inline u16 -r16(struct adm6996_priv *priv, enum admreg reg) -{ - return priv->read(priv, reg); -} - -static inline void -w16(struct adm6996_priv *priv, enum admreg reg, u16 val) -{ - priv->write(priv, reg, val); -} - -/* Minimum timing constants */ -#define EECK_EDGE_TIME 3 /* 3us - max(adm 2.5us, 93c 1us) */ -#define EEDI_SETUP_TIME 1 /* 1us - max(adm 10ns, 93c 400ns) */ -#define EECS_SETUP_TIME 1 /* 1us - max(adm no, 93c 200ns) */ - -static void adm6996_gpio_write(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) -{ - int i, len = (bits + 7) / 8; - u8 mask; - - gpio_set_value(priv->eecs, cs); - udelay(EECK_EDGE_TIME); - - /* Byte assemble from MSB to LSB */ - for (i = 0; i < len; i++) { - /* Bit bang from MSB to LSB */ - for (mask = 0x80; mask && bits > 0; mask >>= 1, bits --) { - /* Clock low */ - gpio_set_value(priv->eesk, 0); - udelay(EECK_EDGE_TIME); - - /* Output on rising edge */ - gpio_set_value(priv->eedi, (mask & buf[i])); - udelay(EEDI_SETUP_TIME); - - /* Clock high */ - gpio_set_value(priv->eesk, 1); - udelay(EECK_EDGE_TIME); - } - } - - /* Clock low */ - gpio_set_value(priv->eesk, 0); - udelay(EECK_EDGE_TIME); - - if (cs) - gpio_set_value(priv->eecs, 0); -} - -static void adm6996_gpio_read(struct adm6996_priv *priv, int cs, char *buf, unsigned int bits) -{ - int i, len = (bits + 7) / 8; - u8 mask; - - gpio_set_value(priv->eecs, cs); - udelay(EECK_EDGE_TIME); - - /* Byte assemble from MSB to LSB */ - for (i = 0; i < len; i++) { - u8 byte; - - /* Bit bang from MSB to LSB */ - for (mask = 0x80, byte = 0; mask && bits > 0; mask >>= 1, bits --) { - u8 gp; - - /* Clock low */ - gpio_set_value(priv->eesk, 0); - udelay(EECK_EDGE_TIME); - - /* Input on rising edge */ - gp = gpio_get_value(priv->eedi); - if (gp) - byte |= mask; - - /* Clock high */ - gpio_set_value(priv->eesk, 1); - udelay(EECK_EDGE_TIME); - } - - *buf++ = byte; - } - - /* Clock low */ - gpio_set_value(priv->eesk, 0); - udelay(EECK_EDGE_TIME); - - if (cs) - gpio_set_value(priv->eecs, 0); -} - -/* Advance clock(s) */ -static void adm6996_gpio_adclk(struct adm6996_priv *priv, int clocks) -{ - int i; - for (i = 0; i < clocks; i++) { - /* Clock high */ - gpio_set_value(priv->eesk, 1); - udelay(EECK_EDGE_TIME); - - /* Clock low */ - gpio_set_value(priv->eesk, 0); - udelay(EECK_EDGE_TIME); - } -} - -static u16 -adm6996_read_gpio_reg(struct adm6996_priv *priv, enum admreg reg) -{ - /* cmd: 01 10 T DD R RRRRRR */ - u8 bits[6] = { - 0xFF, 0xFF, 0xFF, 0xFF, - (0x06 << 4) | ((0 & 0x01) << 3 | (reg&64)>>6), - ((reg&63)<<2) - }; - - u8 rbits[4]; - - /* Enable GPIO outputs with all pins to 0 */ - gpio_direction_output(priv->eecs, 0); - gpio_direction_output(priv->eesk, 0); - gpio_direction_output(priv->eedi, 0); - - adm6996_gpio_write(priv, 0, bits, 46); - gpio_direction_input(priv->eedi); - adm6996_gpio_adclk(priv, 2); - adm6996_gpio_read(priv, 0, rbits, 32); - - /* Extra clock(s) required per datasheet */ - adm6996_gpio_adclk(priv, 2); - - /* Disable GPIO outputs */ - gpio_direction_input(priv->eecs); - gpio_direction_input(priv->eesk); - - /* EEPROM has 16-bit registers, but pumps out two registers in one request */ - return (reg & 0x01 ? (rbits[0]<<8) | rbits[1] : (rbits[2]<<8) | (rbits[3])); -} - -/* Write chip configuration register */ -/* Follow 93c66 timing and chip's min EEPROM timing requirement */ -static void -adm6996_write_gpio_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) -{ - /* cmd(27bits): sb(1) + opc(01) + addr(bbbbbbbb) + data(bbbbbbbbbbbbbbbb) */ - u8 bits[4] = { - (0x05 << 5) | (reg >> 3), - (reg << 5) | (u8)(val >> 11), - (u8)(val >> 3), - (u8)(val << 5) - }; - - /* Enable GPIO outputs with all pins to 0 */ - gpio_direction_output(priv->eecs, 0); - gpio_direction_output(priv->eesk, 0); - gpio_direction_output(priv->eedi, 0); - - /* Write cmd. Total 27 bits */ - adm6996_gpio_write(priv, 1, bits, 27); - - /* Extra clock(s) required per datasheet */ - adm6996_gpio_adclk(priv, 2); - - /* Disable GPIO outputs */ - gpio_direction_input(priv->eecs); - gpio_direction_input(priv->eesk); - gpio_direction_input(priv->eedi); -} - -static u16 -adm6996_read_mii_reg(struct adm6996_priv *priv, enum admreg reg) -{ - struct phy_device *phydev = priv->priv; - struct mii_bus *bus = phydev->mdio.bus; - - return bus->read(bus, PHYADDR(reg)); -} - -static void -adm6996_write_mii_reg(struct adm6996_priv *priv, enum admreg reg, u16 val) -{ - struct phy_device *phydev = priv->priv; - struct mii_bus *bus = phydev->mdio.bus; - - bus->write(bus, PHYADDR(reg), val); -} - -static int -adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - if (val->value.i > 1) - return -EINVAL; - - priv->enable_vlan = val->value.i; - - return 0; -}; - -static int -adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - val->value.i = priv->enable_vlan; - - return 0; -}; - -#ifdef DEBUG - -static int -adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - if (val->value.i > 1023) - return -EINVAL; - - priv->addr = val->value.i; - - return 0; -}; - -static int -adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - val->value.i = priv->addr; - - return 0; -}; - -static int -adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - if (val->value.i > 65535) - return -EINVAL; - - w16(priv, priv->addr, val->value.i); - - return 0; -}; - -static int -adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - val->value.i = r16(priv, priv->addr); - - return 0; -}; - -#endif /* def DEBUG */ - -static int -adm6996_set_pvid(struct switch_dev *dev, int port, int vlan) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("set_pvid port %d vlan %d\n", port, vlan); - - if (vlan > ADM_VLAN_MAX_ID) - return -EINVAL; - - priv->pvid[port] = vlan; - - return 0; -} - -static int -adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("get_pvid port %d\n", port); - *vlan = priv->pvid[port]; - - return 0; -} - -static int -adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("set_vid port %d vid %d\n", val->port_vlan, val->value.i); - - if (val->value.i > ADM_VLAN_MAX_ID) - return -EINVAL; - - priv->vlan_id[val->port_vlan] = val->value.i; - - return 0; -}; - -static int -adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("get_vid port %d\n", val->port_vlan); - - val->value.i = priv->vlan_id[val->port_vlan]; - - return 0; -}; - -static int -adm6996_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - u8 ports = priv->vlan_table[val->port_vlan]; - u8 tagged = priv->vlan_tagged[val->port_vlan]; - int i; - - pr_devel("get_ports port_vlan %d\n", val->port_vlan); - - val->len = 0; - - for (i = 0; i < ADM_NUM_PORTS; i++) { - struct switch_port *p; - - if (!(ports & (1 << i))) - continue; - - p = &val->value.ports[val->len++]; - p->id = i; - if (tagged & (1 << i)) - p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); - else - p->flags = 0; - } - - return 0; -}; - -static int -adm6996_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - u8 *ports = &priv->vlan_table[val->port_vlan]; - u8 *tagged = &priv->vlan_tagged[val->port_vlan]; - int i; - - pr_devel("set_ports port_vlan %d ports", val->port_vlan); - - *ports = 0; - *tagged = 0; - - for (i = 0; i < val->len; i++) { - struct switch_port *p = &val->value.ports[i]; - -#ifdef DEBUG - pr_cont(" %d%s", p->id, - ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" : - "")); -#endif - - if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { - *tagged |= (1 << p->id); - priv->tagged_ports |= (1 << p->id); - } - - *ports |= (1 << p->id); - } - -#ifdef DEBUG - pr_cont("\n"); -#endif - - return 0; -}; - -/* - * Precondition: reg_mutex must be held - */ -static void -adm6996_enable_vlan(struct adm6996_priv *priv) -{ - u16 reg; - - reg = r16(priv, ADM_OTBE_P2_PVID); - reg &= ~(ADM_OTBE_MASK); - w16(priv, ADM_OTBE_P2_PVID, reg); - reg = r16(priv, ADM_IFNTE); - reg &= ~(ADM_IFNTE_MASK); - w16(priv, ADM_IFNTE, reg); - reg = r16(priv, ADM_VID_CHECK); - reg |= ADM_VID_CHECK_MASK; - w16(priv, ADM_VID_CHECK, reg); - reg = r16(priv, ADM_SYSC0); - reg |= ADM_NTTE; - reg &= ~(ADM_RVID1); - w16(priv, ADM_SYSC0, reg); - reg = r16(priv, ADM_SYSC3); - reg |= ADM_TBV; - w16(priv, ADM_SYSC3, reg); -} - -static void -adm6996_enable_vlan_6996l(struct adm6996_priv *priv) -{ - u16 reg; - - reg = r16(priv, ADM_SYSC3); - reg |= ADM_TBV; - reg |= ADM_MAC_CLONE; - w16(priv, ADM_SYSC3, reg); -} - -/* - * Disable VLANs - * - * Sets VLAN mapping for port-based VLAN with all ports connected to - * eachother (this is also the power-on default). - * - * Precondition: reg_mutex must be held - */ -static void -adm6996_disable_vlan(struct adm6996_priv *priv) -{ - u16 reg; - int i; - - for (i = 0; i < ADM_NUM_VLANS; i++) { - reg = ADM_VLAN_FILT_MEMBER_MASK; - w16(priv, ADM_VLAN_FILT_L(i), reg); - reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1); - w16(priv, ADM_VLAN_FILT_H(i), reg); - } - - reg = r16(priv, ADM_OTBE_P2_PVID); - reg |= ADM_OTBE_MASK; - w16(priv, ADM_OTBE_P2_PVID, reg); - reg = r16(priv, ADM_IFNTE); - reg |= ADM_IFNTE_MASK; - w16(priv, ADM_IFNTE, reg); - reg = r16(priv, ADM_VID_CHECK); - reg &= ~(ADM_VID_CHECK_MASK); - w16(priv, ADM_VID_CHECK, reg); - reg = r16(priv, ADM_SYSC0); - reg &= ~(ADM_NTTE); - reg |= ADM_RVID1; - w16(priv, ADM_SYSC0, reg); - reg = r16(priv, ADM_SYSC3); - reg &= ~(ADM_TBV); - w16(priv, ADM_SYSC3, reg); -} - -/* - * Disable VLANs - * - * Sets VLAN mapping for port-based VLAN with all ports connected to - * eachother (this is also the power-on default). - * - * Precondition: reg_mutex must be held - */ -static void -adm6996_disable_vlan_6996l(struct adm6996_priv *priv) -{ - u16 reg; - int i; - - for (i = 0; i < ADM_NUM_VLANS; i++) { - w16(priv, ADM_VLAN_MAP(i), 0); - } - - reg = r16(priv, ADM_SYSC3); - reg &= ~(ADM_TBV); - reg &= ~(ADM_MAC_CLONE); - w16(priv, ADM_SYSC3, reg); -} - -/* - * Precondition: reg_mutex must be held - */ -static void -adm6996_apply_port_pvids(struct adm6996_priv *priv) -{ - u16 reg; - int i; - - for (i = 0; i < ADM_NUM_PORTS; i++) { - reg = r16(priv, adm_portcfg[i]); - reg &= ~(ADM_PORTCFG_PVID_MASK); - reg |= ADM_PORTCFG_PVID(priv->pvid[i]); - if (priv->model == ADM6996L) { - if (priv->tagged_ports & (1 << i)) - reg |= (1 << 4); - else - reg &= ~(1 << 4); - } - w16(priv, adm_portcfg[i], reg); - } - - w16(priv, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0])); - w16(priv, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1])); - reg = r16(priv, ADM_OTBE_P2_PVID); - reg &= ~(ADM_P2_PVID_MASK); - reg |= ADM_P2_PVID_VAL(priv->pvid[2]); - w16(priv, ADM_OTBE_P2_PVID, reg); - reg = ADM_P3_PVID_VAL(priv->pvid[3]); - reg |= ADM_P4_PVID_VAL(priv->pvid[4]); - w16(priv, ADM_P3_P4_PVID, reg); - reg = r16(priv, ADM_P5_PVID); - reg &= ~(ADM_P2_PVID_MASK); - reg |= ADM_P5_PVID_VAL(priv->pvid[5]); - w16(priv, ADM_P5_PVID, reg); -} - -/* - * Precondition: reg_mutex must be held - */ -static void -adm6996_apply_vlan_filters(struct adm6996_priv *priv) -{ - u8 ports, tagged; - u16 vid, reg; - int i; - - for (i = 0; i < ADM_NUM_VLANS; i++) { - vid = priv->vlan_id[i]; - ports = priv->vlan_table[i]; - tagged = priv->vlan_tagged[i]; - - if (ports == 0) { - /* Disable VLAN entry */ - w16(priv, ADM_VLAN_FILT_H(i), 0); - w16(priv, ADM_VLAN_FILT_L(i), 0); - continue; - } - - reg = ADM_VLAN_FILT_MEMBER(ports); - reg |= ADM_VLAN_FILT_TAGGED(tagged); - w16(priv, ADM_VLAN_FILT_L(i), reg); - reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid); - w16(priv, ADM_VLAN_FILT_H(i), reg); - } -} - -static void -adm6996_apply_vlan_filters_6996l(struct adm6996_priv *priv) -{ - u8 ports; - u16 reg; - int i; - - for (i = 0; i < ADM_NUM_VLANS; i++) { - ports = priv->vlan_table[i]; - - if (ports == 0) { - /* Disable VLAN entry */ - w16(priv, ADM_VLAN_MAP(i), 0); - continue; - } else { - reg = ADM_VLAN_FILT(ports); - w16(priv, ADM_VLAN_MAP(i), reg); - } - } -} - -static int -adm6996_hw_apply(struct switch_dev *dev) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("hw_apply\n"); - - mutex_lock(&priv->reg_mutex); - - if (!priv->enable_vlan) { - if (priv->vlan_enabled) { - if (priv->model == ADM6996L) - adm6996_disable_vlan_6996l(priv); - else - adm6996_disable_vlan(priv); - priv->vlan_enabled = 0; - } - goto out; - } - - if (!priv->vlan_enabled) { - if (priv->model == ADM6996L) - adm6996_enable_vlan_6996l(priv); - else - adm6996_enable_vlan(priv); - priv->vlan_enabled = 1; - } - - adm6996_apply_port_pvids(priv); - if (priv->model == ADM6996L) - adm6996_apply_vlan_filters_6996l(priv); - else - adm6996_apply_vlan_filters(priv); - -out: - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -/* - * Reset the switch - * - * The ADM6996 can't do a software-initiated reset, so we just initialise the - * registers we support in this driver. - * - * Precondition: reg_mutex must be held - */ -static void -adm6996_perform_reset (struct adm6996_priv *priv) -{ - int i; - - /* initialize port and vlan settings */ - for (i = 0; i < ADM_NUM_PORTS - 1; i++) { - w16(priv, adm_portcfg[i], ADM_PORTCFG_INIT | - ADM_PORTCFG_PVID(0)); - } - w16(priv, adm_portcfg[5], ADM_PORTCFG_CPU); - - if (priv->model == ADM6996M || priv->model == ADM6996FC) { - /* reset all PHY ports */ - for (i = 0; i < ADM_PHY_PORTS; i++) { - w16(priv, ADM_PHY_PORT(i), ADM_PHYCFG_INIT); - } - } - - priv->enable_vlan = 0; - priv->vlan_enabled = 0; - - for (i = 0; i < ADM_NUM_PORTS; i++) { - priv->pvid[i] = 0; - } - - for (i = 0; i < ADM_NUM_VLANS; i++) { - priv->vlan_id[i] = i; - priv->vlan_table[i] = 0; - priv->vlan_tagged[i] = 0; - } - - if (priv->model == ADM6996M) { - /* Clear VLAN priority map so prio's are unused */ - w16 (priv, ADM_VLAN_PRIOMAP, 0); - - adm6996_disable_vlan(priv); - adm6996_apply_port_pvids(priv); - } else if (priv->model == ADM6996L) { - /* Clear VLAN priority map so prio's are unused */ - w16 (priv, ADM_VLAN_PRIOMAP, 0); - - adm6996_disable_vlan_6996l(priv); - adm6996_apply_port_pvids(priv); - } -} - -static int -adm6996_reset_switch(struct switch_dev *dev) -{ - struct adm6996_priv *priv = to_adm(dev); - - pr_devel("reset\n"); - - mutex_lock(&priv->reg_mutex); - adm6996_perform_reset (priv); - mutex_unlock(&priv->reg_mutex); - return 0; -} - -static int -adm6996_get_port_link(struct switch_dev *dev, int port, - struct switch_port_link *link) -{ - struct adm6996_priv *priv = to_adm(dev); - - u16 reg = 0; - - if (port >= ADM_NUM_PORTS) - return -EINVAL; - - switch (port) { - case 0: - reg = r16(priv, ADM_PS0); - break; - case 1: - reg = r16(priv, ADM_PS0); - reg = reg >> 8; - break; - case 2: - reg = r16(priv, ADM_PS1); - break; - case 3: - reg = r16(priv, ADM_PS1); - reg = reg >> 8; - break; - case 4: - reg = r16(priv, ADM_PS1); - reg = reg >> 12; - break; - case 5: - reg = r16(priv, ADM_PS2); - /* Bits 0, 1, 3 and 4. */ - reg = (reg & 3) | ((reg & 24) >> 1); - break; - default: - return -EINVAL; - } - - link->link = reg & ADM_PS_LS; - if (!link->link) - return 0; - link->aneg = true; - link->duplex = reg & ADM_PS_DS; - link->tx_flow = reg & ADM_PS_FCS; - link->rx_flow = reg & ADM_PS_FCS; - if (reg & ADM_PS_SS) - link->speed = SWITCH_PORT_SPEED_100; - else - link->speed = SWITCH_PORT_SPEED_10; - - return 0; -} - -static int -adm6996_sw_get_port_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct adm6996_priv *priv = to_adm(dev); - int port; - char *buf = priv->buf; - int i, len = 0; - u32 reg = 0; - - port = val->port_vlan; - if (port >= ADM_NUM_PORTS) - return -EINVAL; - - mutex_lock(&priv->mib_lock); - - len += snprintf(buf + len, sizeof(priv->buf) - len, - "Port %d MIB counters\n", - port); - - for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) { - reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port)); - reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16; - len += snprintf(buf + len, sizeof(priv->buf) - len, - "%-12s: %u\n", - adm6996_mibs[i].name, - reg); - } - - mutex_unlock(&priv->mib_lock); - - val->value.s = buf; - val->len = len; - - return 0; -} - -static int -adm6996_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats) -{ - struct adm6996_priv *priv = to_adm(dev); - int id; - u32 reg = 0; - - if (port >= ADM_NUM_PORTS) - return -EINVAL; - - mutex_lock(&priv->mib_lock); - - id = ADM6996_MIB_TXB_ID; - reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); - reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; - stats->tx_bytes = reg; - - id = ADM6996_MIB_RXB_ID; - reg = r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port)); - reg += r16(priv, adm6996_mibs[id].offset + ADM_OFFSET_PORT(port) + 1) << 16; - stats->rx_bytes = reg; - - mutex_unlock(&priv->mib_lock); - - return 0; -} - -static struct switch_attr adm6996_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLANs", - .set = adm6996_set_enable_vlan, - .get = adm6996_get_enable_vlan, - }, -#ifdef DEBUG - { - .type = SWITCH_TYPE_INT, - .name = "addr", - .description = - "Direct register access: set register address (0 - 1023)", - .set = adm6996_set_addr, - .get = adm6996_get_addr, - }, - { - .type = SWITCH_TYPE_INT, - .name = "data", - .description = - "Direct register access: read/write to register (0 - 65535)", - .set = adm6996_set_data, - .get = adm6996_get_data, - }, -#endif /* def DEBUG */ -}; - -static struct switch_attr adm6996_port[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get port's MIB counters", - .set = NULL, - .get = adm6996_sw_get_port_mib, - }, -}; - -static struct switch_attr adm6996_vlan[] = { - { - .type = SWITCH_TYPE_INT, - .name = "vid", - .description = "VLAN ID", - .set = adm6996_set_vid, - .get = adm6996_get_vid, - }, -}; - -static struct switch_dev_ops adm6996_ops = { - .attr_global = { - .attr = adm6996_globals, - .n_attr = ARRAY_SIZE(adm6996_globals), - }, - .attr_port = { - .attr = adm6996_port, - .n_attr = ARRAY_SIZE(adm6996_port), - }, - .attr_vlan = { - .attr = adm6996_vlan, - .n_attr = ARRAY_SIZE(adm6996_vlan), - }, - .get_port_pvid = adm6996_get_pvid, - .set_port_pvid = adm6996_set_pvid, - .get_vlan_ports = adm6996_get_ports, - .set_vlan_ports = adm6996_set_ports, - .apply_config = adm6996_hw_apply, - .reset_switch = adm6996_reset_switch, - .get_port_link = adm6996_get_port_link, - .get_port_stats = adm6996_get_port_stats, -}; - -static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) -{ - struct switch_dev *swdev; - u16 test, old; - - if (!priv->model) { - /* Detect type of chip */ - old = r16(priv, ADM_VID_CHECK); - test = old ^ (1 << 12); - w16(priv, ADM_VID_CHECK, test); - test ^= r16(priv, ADM_VID_CHECK); - if (test & (1 << 12)) { - /* - * Bit 12 of this register is read-only. - * This is the FC model. - */ - priv->model = ADM6996FC; - } else { - /* Bit 12 is read-write. This is the M model. */ - priv->model = ADM6996M; - w16(priv, ADM_VID_CHECK, old); - } - } - - swdev = &priv->dev; - swdev->name = (adm6996_model_name[priv->model]); - swdev->cpu_port = ADM_CPU_PORT; - swdev->ports = ADM_NUM_PORTS; - swdev->vlans = ADM_NUM_VLANS; - swdev->ops = &adm6996_ops; - swdev->alias = alias; - - /* The ADM6996L connected through GPIOs does not support any switch - status calls */ - if (priv->model == ADM6996L) { - adm6996_ops.attr_port.n_attr = 0; - adm6996_ops.get_port_link = NULL; - } - - pr_info ("%s: %s model PHY found.\n", alias, swdev->name); - - mutex_lock(&priv->reg_mutex); - adm6996_perform_reset (priv); - mutex_unlock(&priv->reg_mutex); - - if (priv->model == ADM6996M || priv->model == ADM6996L) { - return register_switch(swdev, netdev); - } - - return -ENODEV; -} - -static int adm6996_config_init(struct phy_device *pdev) -{ - struct adm6996_priv *priv; - int ret; - - pdev->supported = ADVERTISED_100baseT_Full; - pdev->advertising = ADVERTISED_100baseT_Full; - - if (pdev->mdio.addr != 0) { - pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n" - , pdev->attached_dev->name, pdev->mdio.addr); - return 0; - } - - priv = devm_kzalloc(&pdev->mdio.dev, sizeof(struct adm6996_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - mutex_init(&priv->reg_mutex); - mutex_init(&priv->mib_lock); - priv->priv = pdev; - priv->read = adm6996_read_mii_reg; - priv->write = adm6996_write_mii_reg; - - ret = adm6996_switch_init(priv, pdev->attached_dev->name, pdev->attached_dev); - if (ret < 0) - return ret; - - pdev->priv = priv; - - return 0; -} - -/* - * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 - */ -static int adm6996_read_status(struct phy_device *phydev) -{ - phydev->speed = SPEED_100; - phydev->duplex = DUPLEX_FULL; - phydev->link = 1; - - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - - return 0; -} - -/* - * Warning: phydev->priv is NULL if phydev->mdio.addr != 0 - */ -static int adm6996_config_aneg(struct phy_device *phydev) -{ - return 0; -} - -static int adm6996_fixup(struct phy_device *dev) -{ - struct mii_bus *bus = dev->mdio.bus; - u16 reg; - - /* Our custom registers are at PHY addresses 0-10. Claim those. */ - if (dev->mdio.addr > 10) - return 0; - - /* look for the switch on the bus */ - reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK; - if (reg != ADM_SIG0_VAL) - return 0; - - reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK; - if (reg != ADM_SIG1_VAL) - return 0; - - dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL; - - return 0; -} - -static int adm6996_probe(struct phy_device *pdev) -{ - return 0; -} - -static void adm6996_remove(struct phy_device *pdev) -{ - struct adm6996_priv *priv = phy_to_adm(pdev); - - if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) - unregister_switch(&priv->dev); -} - -static int adm6996_soft_reset(struct phy_device *phydev) -{ - /* we don't need an extra reset */ - return 0; -} - -static struct phy_driver adm6996_phy_driver = { - .name = "Infineon ADM6996", - .phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL, - .phy_id_mask = 0xffffffff, - .features = PHY_BASIC_FEATURES, - .probe = adm6996_probe, - .remove = adm6996_remove, - .config_init = &adm6996_config_init, - .config_aneg = &adm6996_config_aneg, - .read_status = &adm6996_read_status, - .soft_reset = adm6996_soft_reset, -}; - -static int adm6996_gpio_probe(struct platform_device *pdev) -{ - struct adm6996_gpio_platform_data *pdata = pdev->dev.platform_data; - struct adm6996_priv *priv; - int ret; - - if (!pdata) - return -EINVAL; - - priv = devm_kzalloc(&pdev->dev, sizeof(struct adm6996_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - mutex_init(&priv->reg_mutex); - mutex_init(&priv->mib_lock); - - priv->eecs = pdata->eecs; - priv->eedi = pdata->eedi; - priv->eesk = pdata->eesk; - - priv->model = pdata->model; - priv->read = adm6996_read_gpio_reg; - priv->write = adm6996_write_gpio_reg; - - ret = devm_gpio_request(&pdev->dev, priv->eecs, "adm_eecs"); - if (ret) - return ret; - ret = devm_gpio_request(&pdev->dev, priv->eedi, "adm_eedi"); - if (ret) - return ret; - ret = devm_gpio_request(&pdev->dev, priv->eesk, "adm_eesk"); - if (ret) - return ret; - - ret = adm6996_switch_init(priv, dev_name(&pdev->dev), NULL); - if (ret < 0) - return ret; - - platform_set_drvdata(pdev, priv); - - return 0; -} - -static int adm6996_gpio_remove(struct platform_device *pdev) -{ - struct adm6996_priv *priv = platform_get_drvdata(pdev); - - if (priv && (priv->model == ADM6996M || priv->model == ADM6996L)) - unregister_switch(&priv->dev); - - return 0; -} - -static struct platform_driver adm6996_gpio_driver = { - .probe = adm6996_gpio_probe, - .remove = adm6996_gpio_remove, - .driver = { - .name = "adm6996_gpio", - }, -}; - -static int __init adm6996_init(void) -{ - int err; - - phy_register_fixup_for_id(PHY_ANY_ID, adm6996_fixup); - err = phy_driver_register(&adm6996_phy_driver, THIS_MODULE); - if (err) - return err; - - err = platform_driver_register(&adm6996_gpio_driver); - if (err) - phy_driver_unregister(&adm6996_phy_driver); - - return err; -} - -static void __exit adm6996_exit(void) -{ - platform_driver_unregister(&adm6996_gpio_driver); - phy_driver_unregister(&adm6996_phy_driver); -} - -module_init(adm6996_init); -module_exit(adm6996_exit); diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.h b/target/linux/generic/files/drivers/net/phy/adm6996.h deleted file mode 100644 index 6fd460a465..0000000000 --- a/target/linux/generic/files/drivers/net/phy/adm6996.h +++ /dev/null @@ -1,186 +0,0 @@ -/* - * ADM6996 switch driver - * - * Copyright (c) 2008 Felix Fietkau - * Copyright (c) 2010,2011 Peter Lebbing - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ -#ifndef __ADM6996_H -#define __ADM6996_H - -/* - * ADM_PHY_PORTS: Number of ports with a PHY. - * We only control ports 0 to 3, because if 4 is connected, it is most likely - * not connected to the switch but to a separate MII and MAC for the WAN port. - */ -#define ADM_PHY_PORTS 4 -#define ADM_NUM_PORTS 6 -#define ADM_CPU_PORT 5 - -#define ADM_NUM_VLANS 16 -#define ADM_VLAN_MAX_ID 4094 - -enum admreg { - ADM_EEPROM_BASE = 0x0, - ADM_P0_CFG = ADM_EEPROM_BASE + 1, - ADM_P1_CFG = ADM_EEPROM_BASE + 3, - ADM_P2_CFG = ADM_EEPROM_BASE + 5, - ADM_P3_CFG = ADM_EEPROM_BASE + 7, - ADM_P4_CFG = ADM_EEPROM_BASE + 8, - ADM_P5_CFG = ADM_EEPROM_BASE + 9, - ADM_SYSC0 = ADM_EEPROM_BASE + 0xa, - ADM_VLAN_PRIOMAP = ADM_EEPROM_BASE + 0xe, - ADM_SYSC3 = ADM_EEPROM_BASE + 0x11, - /* Input Force No Tag Enable */ - ADM_IFNTE = ADM_EEPROM_BASE + 0x20, - ADM_VID_CHECK = ADM_EEPROM_BASE + 0x26, - ADM_P0_PVID = ADM_EEPROM_BASE + 0x28, - ADM_P1_PVID = ADM_EEPROM_BASE + 0x29, - /* Output Tag Bypass Enable and P2 PVID */ - ADM_OTBE_P2_PVID = ADM_EEPROM_BASE + 0x2a, - ADM_P3_P4_PVID = ADM_EEPROM_BASE + 0x2b, - ADM_P5_PVID = ADM_EEPROM_BASE + 0x2c, - ADM_EEPROM_EXT_BASE = 0x40, -#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n)) -#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n)) -#define ADM_VLAN_MAP(n) (ADM_EEPROM_BASE + 0x13 + n) - ADM_COUNTER_BASE = 0xa0, - ADM_SIG0 = ADM_COUNTER_BASE + 0, - ADM_SIG1 = ADM_COUNTER_BASE + 1, - ADM_PS0 = ADM_COUNTER_BASE + 2, - ADM_PS1 = ADM_COUNTER_BASE + 3, - ADM_PS2 = ADM_COUNTER_BASE + 4, - ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */ - ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */ - ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */ - ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */ - ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */ - ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */ -#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2) - ADM_PHY_BASE = 0x200, -#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) -}; - -/* Chip identification patterns */ -#define ADM_SIG0_MASK 0xffff -#define ADM_SIG0_VAL 0x1023 -#define ADM_SIG1_MASK 0xffff -#define ADM_SIG1_VAL 0x0007 - -enum { - ADM_PHYCFG_COLTST = (1 << 7), /* Enable collision test */ - ADM_PHYCFG_DPLX = (1 << 8), /* Enable full duplex */ - ADM_PHYCFG_ANEN_RST = (1 << 9), /* Restart auto negotiation (self clear) */ - ADM_PHYCFG_ISO = (1 << 10), /* Isolate PHY */ - ADM_PHYCFG_PDN = (1 << 11), /* Power down PHY */ - ADM_PHYCFG_ANEN = (1 << 12), /* Enable auto negotiation */ - ADM_PHYCFG_SPEED_100 = (1 << 13), /* Enable 100 Mbit/s */ - ADM_PHYCFG_LPBK = (1 << 14), /* Enable loopback operation */ - ADM_PHYCFG_RST = (1 << 15), /* Reset the port (self clear) */ - ADM_PHYCFG_INIT = ( - ADM_PHYCFG_RST | - ADM_PHYCFG_SPEED_100 | - ADM_PHYCFG_ANEN | - ADM_PHYCFG_ANEN_RST - ) -}; - -enum { - ADM_PORTCFG_FC = (1 << 0), /* Enable 802.x flow control */ - ADM_PORTCFG_AN = (1 << 1), /* Enable auto-negotiation */ - ADM_PORTCFG_SPEED_100 = (1 << 2), /* Enable 100 Mbit/s */ - ADM_PORTCFG_DPLX = (1 << 3), /* Enable full duplex */ - ADM_PORTCFG_OT = (1 << 4), /* Output tagged packets */ - ADM_PORTCFG_PD = (1 << 5), /* Port disable */ - ADM_PORTCFG_TV_PRIO = (1 << 6), /* 0 = VLAN based priority - * 1 = TOS based priority */ - ADM_PORTCFG_PPE = (1 << 7), /* Port based priority enable */ - ADM_PORTCFG_PP_S = (1 << 8), /* Port based priority, 2 bits */ - ADM_PORTCFG_PVID_BASE = (1 << 10), /* Primary VLAN id, 4 bits */ - ADM_PORTCFG_FSE = (1 << 14), /* Fx select enable */ - ADM_PORTCFG_CAM = (1 << 15), /* Crossover Auto MDIX */ - - ADM_PORTCFG_INIT = ( - ADM_PORTCFG_FC | - ADM_PORTCFG_AN | - ADM_PORTCFG_SPEED_100 | - ADM_PORTCFG_DPLX | - ADM_PORTCFG_CAM - ), - ADM_PORTCFG_CPU = ( - ADM_PORTCFG_FC | - ADM_PORTCFG_SPEED_100 | - ADM_PORTCFG_OT | - ADM_PORTCFG_DPLX - ), -}; - -#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8) -#define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10) -#define ADM_PORTCFG_PVID_MASK (0xf << 10) - -#define ADM_IFNTE_MASK (0x3f << 9) -#define ADM_VID_CHECK_MASK (0x3f << 6) - -#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) -#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) -#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) -#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) -#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8) -#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0) -#define ADM_P2_PVID_MASK 0xff - -#define ADM_OTBE(n) (((n) & 0x3f) << 8) -#define ADM_OTBE_MASK (0x3f << 8) - -/* ADM_SYSC0 */ -enum { - ADM_NTTE = (1 << 2), /* New Tag Transmit Enable */ - ADM_RVID1 = (1 << 8) /* Replace VLAN ID 1 */ -}; - -/* Tag Based VLAN in ADM_SYSC3 */ -#define ADM_MAC_CLONE BIT(4) -#define ADM_TBV BIT(5) - -static const u8 adm_portcfg[] = { - [0] = ADM_P0_CFG, - [1] = ADM_P1_CFG, - [2] = ADM_P2_CFG, - [3] = ADM_P3_CFG, - [4] = ADM_P4_CFG, - [5] = ADM_P5_CFG, -}; - -/* Fields in ADM_VLAN_FILT_L(x) */ -#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12) -#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6) -#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0) -#define ADM_VLAN_FILT_MEMBER_MASK 0x3f -/* Fields in ADM_VLAN_FILT_H(x) */ -#define ADM_VLAN_FILT_VALID (1 << 15) -#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0) - -/* Convert ports to a form for ADM6996L VLAN map */ -#define ADM_VLAN_FILT(ports) ((ports & 0x01) | ((ports & 0x02) << 1) | \ - ((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \ - ((ports & 0x10) << 3) | ((ports & 0x20) << 3)) - -/* Port status register */ -enum { - ADM_PS_LS = (1 << 0), /* Link status */ - ADM_PS_SS = (1 << 1), /* Speed status */ - ADM_PS_DS = (1 << 2), /* Duplex status */ - ADM_PS_FCS = (1 << 3) /* Flow control status */ -}; - -/* - * Split the register address in phy id and register - * it will get combined again by the mdio bus op - */ -#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.c b/target/linux/generic/files/drivers/net/phy/ar8216.c deleted file mode 100644 index 7512ee1b43..0000000000 --- a/target/linux/generic/files/drivers/net/phy/ar8216.c +++ /dev/null @@ -1,2313 +0,0 @@ -/* - * ar8216.c: AR8216 switch driver - * - * Copyright (C) 2009 Felix Fietkau - * Copyright (C) 2011-2012 Gabor Juhos - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ar8216.h" - -extern const struct ar8xxx_chip ar8327_chip; -extern const struct ar8xxx_chip ar8337_chip; - -#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ - -#define MIB_DESC(_s , _o, _n) \ - { \ - .size = (_s), \ - .offset = (_o), \ - .name = (_n), \ - } - -static const struct ar8xxx_mib_desc ar8216_mibs[] = { - MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), - MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), - MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), - MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), - MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), - MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), - MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), - MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), - MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), - MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), - MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), - MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), - MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), - MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), - MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), - MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), - MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), - MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), - MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), - MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), - MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), - MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), - MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), - MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), - MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), - MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), - MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), - MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), - MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), - MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), - MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), - MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), - MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), - MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), - MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), - MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), - MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), -}; - -const struct ar8xxx_mib_desc ar8236_mibs[39] = { - MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), - MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), - MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), - MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), - MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), - MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), - MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), - MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), - MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), - MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), - MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), - MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), - MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), - MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), - MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), - MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), - MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), - MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), - MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), - MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), - MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), - MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), - MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), - MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), - MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), - MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), - MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), - MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), - MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), - MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), - MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), - MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), - MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), - MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), - MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), - MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), - MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), - MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), - MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), -}; - -static DEFINE_MUTEX(ar8xxx_dev_list_lock); -static LIST_HEAD(ar8xxx_dev_list); - -/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ -static int -ar8xxx_phy_poll_reset(struct mii_bus *bus) -{ - unsigned int sleep_msecs = 20; - int ret, elapsed, i; - - for (elapsed = sleep_msecs; elapsed <= 600; - elapsed += sleep_msecs) { - msleep(sleep_msecs); - for (i = 0; i < AR8XXX_NUM_PHYS; i++) { - ret = mdiobus_read(bus, i, MII_BMCR); - if (ret < 0) - return ret; - if (ret & BMCR_RESET) - break; - if (i == AR8XXX_NUM_PHYS - 1) { - usleep_range(1000, 2000); - return 0; - } - } - } - return -ETIMEDOUT; -} - -static int -ar8xxx_phy_check_aneg(struct phy_device *phydev) -{ - int ret; - - if (phydev->autoneg != AUTONEG_ENABLE) - return 0; - /* - * BMCR_ANENABLE might have been cleared - * by phy_init_hw in certain kernel versions - * therefore check for it - */ - ret = phy_read(phydev, MII_BMCR); - if (ret < 0) - return ret; - if (ret & BMCR_ANENABLE) - return 0; - - dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); - ret |= BMCR_ANENABLE | BMCR_ANRESTART; - return phy_write(phydev, MII_BMCR, ret); -} - -void -ar8xxx_phy_init(struct ar8xxx_priv *priv) -{ - int i; - struct mii_bus *bus; - - bus = priv->mii_bus; - for (i = 0; i < AR8XXX_NUM_PHYS; i++) { - if (priv->chip->phy_fixup) - priv->chip->phy_fixup(priv, i); - - /* initialize the port itself */ - mdiobus_write(bus, i, MII_ADVERTISE, - ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); - if (ar8xxx_has_gige(priv)) - mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); - mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); - } - - ar8xxx_phy_poll_reset(bus); -} - -u32 -ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) -{ - struct mii_bus *bus = priv->mii_bus; - u16 lo, hi; - - lo = bus->read(bus, phy_id, regnum); - hi = bus->read(bus, phy_id, regnum + 1); - - return (hi << 16) | lo; -} - -void -ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) -{ - struct mii_bus *bus = priv->mii_bus; - u16 lo, hi; - - lo = val & 0xffff; - hi = (u16) (val >> 16); - - if (priv->chip->mii_lo_first) - { - bus->write(bus, phy_id, regnum, lo); - bus->write(bus, phy_id, regnum + 1, hi); - } else { - bus->write(bus, phy_id, regnum + 1, hi); - bus->write(bus, phy_id, regnum, lo); - } -} - -u32 -ar8xxx_read(struct ar8xxx_priv *priv, int reg) -{ - struct mii_bus *bus = priv->mii_bus; - u16 r1, r2, page; - u32 val; - - split_addr((u32) reg, &r1, &r2, &page); - - mutex_lock(&bus->mdio_lock); - - bus->write(bus, 0x18, 0, page); - wait_for_page_switch(); - val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); - - mutex_unlock(&bus->mdio_lock); - - return val; -} - -void -ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) -{ - struct mii_bus *bus = priv->mii_bus; - u16 r1, r2, page; - - split_addr((u32) reg, &r1, &r2, &page); - - mutex_lock(&bus->mdio_lock); - - bus->write(bus, 0x18, 0, page); - wait_for_page_switch(); - ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); - - mutex_unlock(&bus->mdio_lock); -} - -u32 -ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) -{ - struct mii_bus *bus = priv->mii_bus; - u16 r1, r2, page; - u32 ret; - - split_addr((u32) reg, &r1, &r2, &page); - - mutex_lock(&bus->mdio_lock); - - bus->write(bus, 0x18, 0, page); - wait_for_page_switch(); - - ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); - ret &= ~mask; - ret |= val; - ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); - - mutex_unlock(&bus->mdio_lock); - - return ret; -} - -void -ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, - u16 dbg_addr, u16 dbg_data) -{ - struct mii_bus *bus = priv->mii_bus; - - mutex_lock(&bus->mdio_lock); - bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); - bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); - mutex_unlock(&bus->mdio_lock); -} - -static inline void -ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) -{ - bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); - bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); - bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); -} - -void -ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) -{ - struct mii_bus *bus = priv->mii_bus; - - mutex_lock(&bus->mdio_lock); - ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); - bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); - mutex_unlock(&bus->mdio_lock); -} - -u16 -ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) -{ - struct mii_bus *bus = priv->mii_bus; - u16 data; - - mutex_lock(&bus->mdio_lock); - ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); - data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); - mutex_unlock(&bus->mdio_lock); - - return data; -} - -static int -ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, - unsigned timeout) -{ - int i; - - for (i = 0; i < timeout; i++) { - u32 t; - - t = ar8xxx_read(priv, reg); - if ((t & mask) == val) - return 0; - - usleep_range(1000, 2000); - cond_resched(); - } - - return -ETIMEDOUT; -} - -static int -ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) -{ - unsigned mib_func = priv->chip->mib_func; - int ret; - - lockdep_assert_held(&priv->mib_lock); - - /* Capture the hardware statistics for all ports */ - ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); - - /* Wait for the capturing to complete. */ - ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); - if (ret) - goto out; - - ret = 0; - -out: - return ret; -} - -static int -ar8xxx_mib_capture(struct ar8xxx_priv *priv) -{ - return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); -} - -static int -ar8xxx_mib_flush(struct ar8xxx_priv *priv) -{ - return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); -} - -static void -ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) -{ - unsigned int base; - u64 *mib_stats; - int i; - - WARN_ON(port >= priv->dev.ports); - - lockdep_assert_held(&priv->mib_lock); - - base = priv->chip->reg_port_stats_start + - priv->chip->reg_port_stats_length * port; - - mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; - for (i = 0; i < priv->chip->num_mibs; i++) { - const struct ar8xxx_mib_desc *mib; - u64 t; - - mib = &priv->chip->mib_decs[i]; - t = ar8xxx_read(priv, base + mib->offset); - if (mib->size == 2) { - u64 hi; - - hi = ar8xxx_read(priv, base + mib->offset + 4); - t |= hi << 32; - } - - if (flush) - mib_stats[i] = 0; - else - mib_stats[i] += t; - cond_resched(); - } -} - -static void -ar8216_read_port_link(struct ar8xxx_priv *priv, int port, - struct switch_port_link *link) -{ - u32 status; - u32 speed; - - memset(link, '\0', sizeof(*link)); - - status = priv->chip->read_port_status(priv, port); - - link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); - if (link->aneg) { - link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); - } else { - link->link = true; - - if (priv->get_port_link) { - int err; - - err = priv->get_port_link(port); - if (err >= 0) - link->link = !!err; - } - } - - if (!link->link) - return; - - link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); - link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); - link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); - - if (link->aneg && link->duplex && priv->chip->read_port_eee_status) - link->eee = priv->chip->read_port_eee_status(priv, port); - - speed = (status & AR8216_PORT_STATUS_SPEED) >> - AR8216_PORT_STATUS_SPEED_S; - - switch (speed) { - case AR8216_PORT_SPEED_10M: - link->speed = SWITCH_PORT_SPEED_10; - break; - case AR8216_PORT_SPEED_100M: - link->speed = SWITCH_PORT_SPEED_100; - break; - case AR8216_PORT_SPEED_1000M: - link->speed = SWITCH_PORT_SPEED_1000; - break; - default: - link->speed = SWITCH_PORT_SPEED_UNKNOWN; - break; - } -} - -static struct sk_buff * -ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) -{ - struct ar8xxx_priv *priv = dev->phy_ptr; - unsigned char *buf; - - if (unlikely(!priv)) - goto error; - - if (!priv->vlan) - goto send; - - if (unlikely(skb_headroom(skb) < 2)) { - if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) - goto error; - } - - buf = skb_push(skb, 2); - buf[0] = 0x10; - buf[1] = 0x80; - -send: - return skb; - -error: - dev_kfree_skb_any(skb); - return NULL; -} - -static void -ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) -{ - struct ar8xxx_priv *priv; - unsigned char *buf; - int port, vlan; - - priv = dev->phy_ptr; - if (!priv) - return; - - /* don't strip the header if vlan mode is disabled */ - if (!priv->vlan) - return; - - /* strip header, get vlan id */ - buf = skb->data; - skb_pull(skb, 2); - - /* check for vlan header presence */ - if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) - return; - - port = buf[0] & 0x7; - - /* no need to fix up packets coming from a tagged source */ - if (priv->vlan_tagged & (1 << port)) - return; - - /* lookup port vid from local table, the switch passes an invalid vlan id */ - vlan = priv->vlan_id[priv->pvid[port]]; - - buf[14 + 2] &= 0xf0; - buf[14 + 2] |= vlan >> 8; - buf[15 + 2] = vlan & 0xff; -} - -int -ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) -{ - int timeout = 20; - u32 t = 0; - - while (1) { - t = ar8xxx_read(priv, reg); - if ((t & mask) == val) - return 0; - - if (timeout-- <= 0) - break; - - udelay(10); - cond_resched(); - } - - pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", - (unsigned int) reg, t, mask, val); - return -ETIMEDOUT; -} - -static void -ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) -{ - if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) - return; - if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { - val &= AR8216_VTUDATA_MEMBER; - val |= AR8216_VTUDATA_VALID; - ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); - } - op |= AR8216_VTU_ACTIVE; - ar8xxx_write(priv, AR8216_REG_VTU, op); -} - -static void -ar8216_vtu_flush(struct ar8xxx_priv *priv) -{ - ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); -} - -static void -ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) -{ - u32 op; - - op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); - ar8216_vtu_op(priv, op, port_mask); -} - -static int -ar8216_atu_flush(struct ar8xxx_priv *priv) -{ - int ret; - - ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); - if (!ret) - ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | - AR8216_ATU_ACTIVE); - - return ret; -} - -static int -ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) -{ - u32 t; - int ret; - - ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); - if (!ret) { - t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; - t |= AR8216_ATU_ACTIVE; - ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); - } - - return ret; -} - -static u32 -ar8216_read_port_status(struct ar8xxx_priv *priv, int port) -{ - return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); -} - -static void -ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) -{ - u32 header; - u32 egress, ingress; - u32 pvid; - - if (priv->vlan) { - pvid = priv->vlan_id[priv->pvid[port]]; - if (priv->vlan_tagged & (1 << port)) - egress = AR8216_OUT_ADD_VLAN; - else - egress = AR8216_OUT_STRIP_VLAN; - ingress = AR8216_IN_SECURE; - } else { - pvid = port; - egress = AR8216_OUT_KEEP; - ingress = AR8216_IN_PORT_ONLY; - } - - if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) - header = AR8216_PORT_CTRL_HEADER; - else - header = 0; - - ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), - AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | - AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | - AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, - AR8216_PORT_CTRL_LEARN | header | - (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | - (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); - - ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), - AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | - AR8216_PORT_VLAN_DEFAULT_ID, - (members << AR8216_PORT_VLAN_DEST_PORTS_S) | - (ingress << AR8216_PORT_VLAN_MODE_S) | - (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); -} - -static int -ar8216_hw_init(struct ar8xxx_priv *priv) -{ - if (priv->initialized) - return 0; - - ar8xxx_phy_init(priv); - - priv->initialized = true; - return 0; -} - -static void -ar8216_init_globals(struct ar8xxx_priv *priv) -{ - /* standard atheros magic */ - ar8xxx_write(priv, 0x38, 0xc000050e); - - ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, - AR8216_GCTRL_MTU, 1518 + 8 + 2); -} - -static void -ar8216_init_port(struct ar8xxx_priv *priv, int port) -{ - /* Enable port learning and tx */ - ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), - AR8216_PORT_CTRL_LEARN | - (4 << AR8216_PORT_CTRL_STATE_S)); - - ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); - - if (port == AR8216_PORT_CPU) { - ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), - AR8216_PORT_STATUS_LINK_UP | - (ar8xxx_has_gige(priv) ? - AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | - AR8216_PORT_STATUS_TXMAC | - AR8216_PORT_STATUS_RXMAC | - (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | - (chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | - AR8216_PORT_STATUS_DUPLEX); - } else { - ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), - AR8216_PORT_STATUS_LINK_AUTO); - } -} - -static void -ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) -{ - int timeout = 20; - - while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { - udelay(10); - cond_resched(); - } - - if (!timeout) - pr_err("ar8216: timeout waiting for atu to become ready\n"); -} - -static void ar8216_get_arl_entry(struct ar8xxx_priv *priv, - struct arl_entry *a, u32 *status, enum arl_op op) -{ - struct mii_bus *bus = priv->mii_bus; - u16 r2, page; - u16 r1_func0, r1_func1, r1_func2; - u32 t, val0, val1, val2; - int i; - - split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); - r2 |= 0x10; - - r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; - r1_func2 = (AR8216_REG_ATU_FUNC2 >> 1) & 0x1e; - - switch (op) { - case AR8XXX_ARL_INITIALIZE: - /* all ATU registers are on the same page - * therefore set page only once - */ - bus->write(bus, 0x18, 0, page); - wait_for_page_switch(); - - ar8216_wait_atu_ready(priv, r2, r1_func0); - - ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); - ar8xxx_mii_write32(priv, r2, r1_func1, 0); - ar8xxx_mii_write32(priv, r2, r1_func2, 0); - break; - case AR8XXX_ARL_GET_NEXT: - t = ar8xxx_mii_read32(priv, r2, r1_func0); - t |= AR8216_ATU_ACTIVE; - ar8xxx_mii_write32(priv, r2, r1_func0, t); - ar8216_wait_atu_ready(priv, r2, r1_func0); - - val0 = ar8xxx_mii_read32(priv, r2, r1_func0); - val1 = ar8xxx_mii_read32(priv, r2, r1_func1); - val2 = ar8xxx_mii_read32(priv, r2, r1_func2); - - *status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; - if (!*status) - break; - - i = 0; - t = AR8216_ATU_PORT0; - while (!(val2 & t) && ++i < priv->dev.ports) - t <<= 1; - - a->port = i; - a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; - a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; - a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; - a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; - a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; - a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; - break; - } -} - -static void -ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) -{ - u32 egress, ingress; - u32 pvid; - - if (priv->vlan) { - pvid = priv->vlan_id[priv->pvid[port]]; - if (priv->vlan_tagged & (1 << port)) - egress = AR8216_OUT_ADD_VLAN; - else - egress = AR8216_OUT_STRIP_VLAN; - ingress = AR8216_IN_SECURE; - } else { - pvid = port; - egress = AR8216_OUT_KEEP; - ingress = AR8216_IN_PORT_ONLY; - } - - ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), - AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | - AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | - AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, - AR8216_PORT_CTRL_LEARN | - (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | - (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); - - ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), - AR8236_PORT_VLAN_DEFAULT_ID, - (pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); - - ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), - AR8236_PORT_VLAN2_VLAN_MODE | - AR8236_PORT_VLAN2_MEMBER, - (ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | - (members << AR8236_PORT_VLAN2_MEMBER_S)); -} - -static void -ar8236_init_globals(struct ar8xxx_priv *priv) -{ - /* enable jumbo frames */ - ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, - AR8316_GCTRL_MTU, 9018 + 8 + 2); - - /* enable cpu port to receive arp frames */ - ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, - AR8236_ATU_CTRL_RES); - - /* enable cpu port to receive multicast and broadcast frames */ - ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, - AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); - - /* Enable MIB counters */ - ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, - (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | - AR8236_MIB_EN); -} - -static int -ar8316_hw_init(struct ar8xxx_priv *priv) -{ - u32 val, newval; - - val = ar8xxx_read(priv, AR8316_REG_POSTRIP); - - if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { - if (priv->port4_phy) { - /* value taken from Ubiquiti RouterStation Pro */ - newval = 0x81461bea; - pr_info("ar8316: Using port 4 as PHY\n"); - } else { - newval = 0x01261be2; - pr_info("ar8316: Using port 4 as switch port\n"); - } - } else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { - /* value taken from AVM Fritz!Box 7390 sources */ - newval = 0x010e5b71; - } else { - /* no known value for phy interface */ - pr_err("ar8316: unsupported mii mode: %d.\n", - priv->phy->interface); - return -EINVAL; - } - - if (val == newval) - goto out; - - ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); - - if (priv->port4_phy && - priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { - /* work around for phy4 rgmii mode */ - ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); - /* rx delay */ - ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); - /* tx delay */ - ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); - msleep(1000); - } - - ar8xxx_phy_init(priv); - -out: - priv->initialized = true; - return 0; -} - -static void -ar8316_init_globals(struct ar8xxx_priv *priv) -{ - /* standard atheros magic */ - ar8xxx_write(priv, 0x38, 0xc000050e); - - /* enable cpu port to receive multicast and broadcast frames */ - ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); - - /* enable jumbo frames */ - ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, - AR8316_GCTRL_MTU, 9018 + 8 + 2); - - /* Enable MIB counters */ - ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, - (AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | - AR8236_MIB_EN); -} - -int -ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - priv->vlan = !!val->value.i; - return 0; -} - -int -ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->vlan; - return 0; -} - - -int -ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - /* make sure no invalid PVIDs get set */ - - if (vlan < 0 || vlan >= dev->vlans || - port < 0 || port >= AR8X16_MAX_PORTS) - return -EINVAL; - - priv->pvid[port] = vlan; - return 0; -} - -int -ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - if (port < 0 || port >= AR8X16_MAX_PORTS) - return -EINVAL; - - *vlan = priv->pvid[port]; - return 0; -} - -static int -ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - if (val->port_vlan >= AR8X16_MAX_VLANS) - return -EINVAL; - - priv->vlan_id[val->port_vlan] = val->value.i; - return 0; -} - -static int -ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->vlan_id[val->port_vlan]; - return 0; -} - -int -ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, - struct switch_port_link *link) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - ar8216_read_port_link(priv, port, link); - return 0; -} - -static int -ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - u8 ports; - int i; - - if (val->port_vlan >= AR8X16_MAX_VLANS) - return -EINVAL; - - ports = priv->vlan_table[val->port_vlan]; - val->len = 0; - for (i = 0; i < dev->ports; i++) { - struct switch_port *p; - - if (!(ports & (1 << i))) - continue; - - p = &val->value.ports[val->len++]; - p->id = i; - if (priv->vlan_tagged & (1 << i)) - p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); - else - p->flags = 0; - } - return 0; -} - -static int -ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - u8 *vt = &priv->vlan_table[val->port_vlan]; - int i, j; - - *vt = 0; - for (i = 0; i < val->len; i++) { - struct switch_port *p = &val->value.ports[i]; - - if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { - priv->vlan_tagged |= (1 << p->id); - } else { - priv->vlan_tagged &= ~(1 << p->id); - priv->pvid[p->id] = val->port_vlan; - - /* make sure that an untagged port does not - * appear in other vlans */ - for (j = 0; j < AR8X16_MAX_VLANS; j++) { - if (j == val->port_vlan) - continue; - priv->vlan_table[j] &= ~(1 << p->id); - } - } - - *vt |= 1 << p->id; - } - return 0; -} - -static void -ar8216_set_mirror_regs(struct ar8xxx_priv *priv) -{ - int port; - - /* reset all mirror registers */ - ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, - AR8216_GLOBAL_CPUPORT_MIRROR_PORT, - (0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); - for (port = 0; port < AR8216_NUM_PORTS; port++) { - ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), - AR8216_PORT_CTRL_MIRROR_RX); - - ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), - AR8216_PORT_CTRL_MIRROR_TX); - } - - /* now enable mirroring if necessary */ - if (priv->source_port >= AR8216_NUM_PORTS || - priv->monitor_port >= AR8216_NUM_PORTS || - priv->source_port == priv->monitor_port) { - return; - } - - ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, - AR8216_GLOBAL_CPUPORT_MIRROR_PORT, - (priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); - - if (priv->mirror_rx) - ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), - AR8216_PORT_CTRL_MIRROR_RX); - - if (priv->mirror_tx) - ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), - AR8216_PORT_CTRL_MIRROR_TX); -} - -static inline u32 -ar8xxx_age_time_val(int age_time) -{ - return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / - AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; -} - -static inline void -ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) -{ - u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); - ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); -} - -int -ar8xxx_sw_hw_apply(struct switch_dev *dev) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - const struct ar8xxx_chip *chip = priv->chip; - u8 portmask[AR8X16_MAX_PORTS]; - int i, j; - - mutex_lock(&priv->reg_mutex); - /* flush all vlan translation unit entries */ - priv->chip->vtu_flush(priv); - - memset(portmask, 0, sizeof(portmask)); - if (!priv->init) { - /* calculate the port destination masks and load vlans - * into the vlan translation unit */ - for (j = 0; j < AR8X16_MAX_VLANS; j++) { - u8 vp = priv->vlan_table[j]; - - if (!vp) - continue; - - for (i = 0; i < dev->ports; i++) { - u8 mask = (1 << i); - if (vp & mask) - portmask[i] |= vp & ~mask; - } - - chip->vtu_load_vlan(priv, priv->vlan_id[j], - priv->vlan_table[j]); - } - } else { - /* vlan disabled: - * isolate all ports, but connect them to the cpu port */ - for (i = 0; i < dev->ports; i++) { - if (i == AR8216_PORT_CPU) - continue; - - portmask[i] = 1 << AR8216_PORT_CPU; - portmask[AR8216_PORT_CPU] |= (1 << i); - } - } - - /* update the port destination mask registers and tag settings */ - for (i = 0; i < dev->ports; i++) { - chip->setup_port(priv, i, portmask[i]); - } - - chip->set_mirror_regs(priv); - - /* set age time */ - if (chip->reg_arl_ctrl) - ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); - - mutex_unlock(&priv->reg_mutex); - return 0; -} - -int -ar8xxx_sw_reset_switch(struct switch_dev *dev) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - const struct ar8xxx_chip *chip = priv->chip; - int i; - - mutex_lock(&priv->reg_mutex); - memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - - offsetof(struct ar8xxx_priv, vlan)); - - for (i = 0; i < AR8X16_MAX_VLANS; i++) - priv->vlan_id[i] = i; - - /* Configure all ports */ - for (i = 0; i < dev->ports; i++) - chip->init_port(priv, i); - - priv->mirror_rx = false; - priv->mirror_tx = false; - priv->source_port = 0; - priv->monitor_port = 0; - priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; - - chip->init_globals(priv); - chip->atu_flush(priv); - - mutex_unlock(&priv->reg_mutex); - - return chip->sw_hw_apply(dev); -} - -int -ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - unsigned int len; - int ret; - - if (!ar8xxx_has_mib_counters(priv)) - return -EOPNOTSUPP; - - mutex_lock(&priv->mib_lock); - - len = priv->dev.ports * priv->chip->num_mibs * - sizeof(*priv->mib_stats); - memset(priv->mib_stats, '\0', len); - ret = ar8xxx_mib_flush(priv); - if (ret) - goto unlock; - - ret = 0; - -unlock: - mutex_unlock(&priv->mib_lock); - return ret; -} - -int -ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - mutex_lock(&priv->reg_mutex); - priv->mirror_rx = !!val->value.i; - priv->chip->set_mirror_regs(priv); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->mirror_rx; - return 0; -} - -int -ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - mutex_lock(&priv->reg_mutex); - priv->mirror_tx = !!val->value.i; - priv->chip->set_mirror_regs(priv); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->mirror_tx; - return 0; -} - -int -ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - mutex_lock(&priv->reg_mutex); - priv->monitor_port = val->value.i; - priv->chip->set_mirror_regs(priv); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->monitor_port; - return 0; -} - -int -ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - mutex_lock(&priv->reg_mutex); - priv->source_port = val->value.i; - priv->chip->set_mirror_regs(priv); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->source_port; - return 0; -} - -int -ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port; - int ret; - - if (!ar8xxx_has_mib_counters(priv)) - return -EOPNOTSUPP; - - port = val->port_vlan; - if (port >= dev->ports) - return -EINVAL; - - mutex_lock(&priv->mib_lock); - ret = ar8xxx_mib_capture(priv); - if (ret) - goto unlock; - - ar8xxx_mib_fetch_port_stat(priv, port, true); - - ret = 0; - -unlock: - mutex_unlock(&priv->mib_lock); - return ret; -} - -static void -ar8xxx_byte_to_str(char *buf, int len, u64 byte) -{ - unsigned long b; - const char *unit; - - if (byte >= 0x40000000) { /* 1 GiB */ - b = byte * 10 / 0x40000000; - unit = "GiB"; - } else if (byte >= 0x100000) { /* 1 MiB */ - b = byte * 10 / 0x100000; - unit = "MiB"; - } else if (byte >= 0x400) { /* 1 KiB */ - b = byte * 10 / 0x400; - unit = "KiB"; - } else { - b = byte; - unit = "Byte"; - } - if (strcmp(unit, "Byte")) - snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); - else - snprintf(buf, len, "%lu %s", b, unit); -} - -int -ar8xxx_sw_get_port_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - const struct ar8xxx_chip *chip = priv->chip; - u64 *mib_stats, mib_data; - unsigned int port; - int ret; - char *buf = priv->buf; - char buf1[64]; - const char *mib_name; - int i, len = 0; - bool mib_stats_empty = true; - - if (!ar8xxx_has_mib_counters(priv)) - return -EOPNOTSUPP; - - port = val->port_vlan; - if (port >= dev->ports) - return -EINVAL; - - mutex_lock(&priv->mib_lock); - ret = ar8xxx_mib_capture(priv); - if (ret) - goto unlock; - - ar8xxx_mib_fetch_port_stat(priv, port, false); - - len += snprintf(buf + len, sizeof(priv->buf) - len, - "MIB counters\n"); - - mib_stats = &priv->mib_stats[port * chip->num_mibs]; - for (i = 0; i < chip->num_mibs; i++) { - mib_name = chip->mib_decs[i].name; - mib_data = mib_stats[i]; - len += snprintf(buf + len, sizeof(priv->buf) - len, - "%-12s: %llu\n", mib_name, mib_data); - if ((!strcmp(mib_name, "TxByte") || - !strcmp(mib_name, "RxGoodByte")) && - mib_data >= 1024) { - ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); - --len; /* discard newline at the end of buf */ - len += snprintf(buf + len, sizeof(priv->buf) - len, - " (%s)\n", buf1); - } - if (mib_stats_empty && mib_data) - mib_stats_empty = false; - } - - if (mib_stats_empty) - len = snprintf(buf, sizeof(priv->buf), "No MIB data"); - - val->value.s = buf; - val->len = len; - - ret = 0; - -unlock: - mutex_unlock(&priv->mib_lock); - return ret; -} - -int -ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int age_time = val->value.i; - u32 age_time_val; - - if (age_time < 0) - return -EINVAL; - - age_time_val = ar8xxx_age_time_val(age_time); - if (age_time_val == 0 || age_time_val > 0xffff) - return -EINVAL; - - priv->arl_age_time = age_time; - return 0; -} - -int -ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - val->value.i = priv->arl_age_time; - return 0; -} - -int -ar8xxx_sw_get_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - struct mii_bus *bus = priv->mii_bus; - const struct ar8xxx_chip *chip = priv->chip; - char *buf = priv->arl_buf; - int i, j, k, len = 0; - struct arl_entry *a, *a1; - u32 status; - - if (!chip->get_arl_entry) - return -EOPNOTSUPP; - - mutex_lock(&priv->reg_mutex); - mutex_lock(&bus->mdio_lock); - - chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); - - for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { - a = &priv->arl_table[i]; - duplicate: - chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); - - if (!status) - break; - - /* avoid duplicates - * ARL table can include multiple valid entries - * per MAC, just with differing status codes - */ - for (j = 0; j < i; ++j) { - a1 = &priv->arl_table[j]; - if (a->port == a1->port && !memcmp(a->mac, a1->mac, sizeof(a->mac))) - goto duplicate; - } - } - - mutex_unlock(&bus->mdio_lock); - - len += snprintf(buf + len, sizeof(priv->arl_buf) - len, - "address resolution table\n"); - - if (i == AR8XXX_NUM_ARL_RECORDS) - len += snprintf(buf + len, sizeof(priv->arl_buf) - len, - "Too many entries found, displaying the first %d only!\n", - AR8XXX_NUM_ARL_RECORDS); - - for (j = 0; j < priv->dev.ports; ++j) { - for (k = 0; k < i; ++k) { - a = &priv->arl_table[k]; - if (a->port != j) - continue; - len += snprintf(buf + len, sizeof(priv->arl_buf) - len, - "Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", - j, - a->mac[5], a->mac[4], a->mac[3], - a->mac[2], a->mac[1], a->mac[0]); - } - } - - val->value.s = buf; - val->len = len; - - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int ret; - - mutex_lock(&priv->reg_mutex); - ret = priv->chip->atu_flush(priv); - mutex_unlock(&priv->reg_mutex); - - return ret; -} - -int -ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port, ret; - - port = val->port_vlan; - if (port >= dev->ports) - return -EINVAL; - - mutex_lock(&priv->reg_mutex); - ret = priv->chip->atu_flush_port(priv, port); - mutex_unlock(&priv->reg_mutex); - - return ret; -} - -static const struct switch_attr ar8xxx_sw_attr_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = ar8xxx_sw_set_vlan, - .get = ar8xxx_sw_get_vlan, - .max = 1 - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = ar8xxx_sw_set_reset_mibs, - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_rx", - .description = "Enable mirroring of RX packets", - .set = ar8xxx_sw_set_mirror_rx_enable, - .get = ar8xxx_sw_get_mirror_rx_enable, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_tx", - .description = "Enable mirroring of TX packets", - .set = ar8xxx_sw_set_mirror_tx_enable, - .get = ar8xxx_sw_get_mirror_tx_enable, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "mirror_monitor_port", - .description = "Mirror monitor port", - .set = ar8xxx_sw_set_mirror_monitor_port, - .get = ar8xxx_sw_get_mirror_monitor_port, - .max = AR8216_NUM_PORTS - 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "mirror_source_port", - .description = "Mirror source port", - .set = ar8xxx_sw_set_mirror_source_port, - .get = ar8xxx_sw_get_mirror_source_port, - .max = AR8216_NUM_PORTS - 1 - }, - { - .type = SWITCH_TYPE_STRING, - .name = "arl_table", - .description = "Get ARL table", - .set = NULL, - .get = ar8xxx_sw_get_arl_table, - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "flush_arl_table", - .description = "Flush ARL table", - .set = ar8xxx_sw_set_flush_arl_table, - }, -}; - -const struct switch_attr ar8xxx_sw_attr_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = ar8xxx_sw_set_port_reset_mib, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get port's MIB counters", - .set = NULL, - .get = ar8xxx_sw_get_port_mib, - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "flush_arl_table", - .description = "Flush port's ARL table entries", - .set = ar8xxx_sw_set_flush_port_arl_table, - }, -}; - -const struct switch_attr ar8xxx_sw_attr_vlan[1] = { - { - .type = SWITCH_TYPE_INT, - .name = "vid", - .description = "VLAN ID (0-4094)", - .set = ar8xxx_sw_set_vid, - .get = ar8xxx_sw_get_vid, - .max = 4094, - }, -}; - -static const struct switch_dev_ops ar8xxx_sw_ops = { - .attr_global = { - .attr = ar8xxx_sw_attr_globals, - .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), - }, - .attr_port = { - .attr = ar8xxx_sw_attr_port, - .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), - }, - .attr_vlan = { - .attr = ar8xxx_sw_attr_vlan, - .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), - }, - .get_port_pvid = ar8xxx_sw_get_pvid, - .set_port_pvid = ar8xxx_sw_set_pvid, - .get_vlan_ports = ar8xxx_sw_get_ports, - .set_vlan_ports = ar8xxx_sw_set_ports, - .apply_config = ar8xxx_sw_hw_apply, - .reset_switch = ar8xxx_sw_reset_switch, - .get_port_link = ar8xxx_sw_get_port_link, -/* The following op is disabled as it hogs the CPU and degrades performance. - An implementation has been attempted in 4d8a66d but reading MIB data is slow - on ar8xxx switches. - - The high CPU load has been traced down to the ar8xxx_reg_wait() call in - ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by - the request to update the MIB counter is cleared. */ -#if 0 - .get_port_stats = ar8xxx_sw_get_port_stats, -#endif -}; - -static const struct ar8xxx_chip ar8216_chip = { - .caps = AR8XXX_CAP_MIB_COUNTERS, - - .reg_port_stats_start = 0x19000, - .reg_port_stats_length = 0xa0, - .reg_arl_ctrl = AR8216_REG_ATU_CTRL, - - .name = "Atheros AR8216", - .ports = AR8216_NUM_PORTS, - .vlans = AR8216_NUM_VLANS, - .swops = &ar8xxx_sw_ops, - - .hw_init = ar8216_hw_init, - .init_globals = ar8216_init_globals, - .init_port = ar8216_init_port, - .setup_port = ar8216_setup_port, - .read_port_status = ar8216_read_port_status, - .atu_flush = ar8216_atu_flush, - .atu_flush_port = ar8216_atu_flush_port, - .vtu_flush = ar8216_vtu_flush, - .vtu_load_vlan = ar8216_vtu_load_vlan, - .set_mirror_regs = ar8216_set_mirror_regs, - .get_arl_entry = ar8216_get_arl_entry, - .sw_hw_apply = ar8xxx_sw_hw_apply, - - .num_mibs = ARRAY_SIZE(ar8216_mibs), - .mib_decs = ar8216_mibs, - .mib_func = AR8216_REG_MIB_FUNC -}; - -static const struct ar8xxx_chip ar8236_chip = { - .caps = AR8XXX_CAP_MIB_COUNTERS, - - .reg_port_stats_start = 0x20000, - .reg_port_stats_length = 0x100, - .reg_arl_ctrl = AR8216_REG_ATU_CTRL, - - .name = "Atheros AR8236", - .ports = AR8216_NUM_PORTS, - .vlans = AR8216_NUM_VLANS, - .swops = &ar8xxx_sw_ops, - - .hw_init = ar8216_hw_init, - .init_globals = ar8236_init_globals, - .init_port = ar8216_init_port, - .setup_port = ar8236_setup_port, - .read_port_status = ar8216_read_port_status, - .atu_flush = ar8216_atu_flush, - .atu_flush_port = ar8216_atu_flush_port, - .vtu_flush = ar8216_vtu_flush, - .vtu_load_vlan = ar8216_vtu_load_vlan, - .set_mirror_regs = ar8216_set_mirror_regs, - .get_arl_entry = ar8216_get_arl_entry, - .sw_hw_apply = ar8xxx_sw_hw_apply, - - .num_mibs = ARRAY_SIZE(ar8236_mibs), - .mib_decs = ar8236_mibs, - .mib_func = AR8216_REG_MIB_FUNC -}; - -static const struct ar8xxx_chip ar8316_chip = { - .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, - - .reg_port_stats_start = 0x20000, - .reg_port_stats_length = 0x100, - .reg_arl_ctrl = AR8216_REG_ATU_CTRL, - - .name = "Atheros AR8316", - .ports = AR8216_NUM_PORTS, - .vlans = AR8X16_MAX_VLANS, - .swops = &ar8xxx_sw_ops, - - .hw_init = ar8316_hw_init, - .init_globals = ar8316_init_globals, - .init_port = ar8216_init_port, - .setup_port = ar8216_setup_port, - .read_port_status = ar8216_read_port_status, - .atu_flush = ar8216_atu_flush, - .atu_flush_port = ar8216_atu_flush_port, - .vtu_flush = ar8216_vtu_flush, - .vtu_load_vlan = ar8216_vtu_load_vlan, - .set_mirror_regs = ar8216_set_mirror_regs, - .get_arl_entry = ar8216_get_arl_entry, - .sw_hw_apply = ar8xxx_sw_hw_apply, - - .num_mibs = ARRAY_SIZE(ar8236_mibs), - .mib_decs = ar8236_mibs, - .mib_func = AR8216_REG_MIB_FUNC -}; - -static int -ar8xxx_id_chip(struct ar8xxx_priv *priv) -{ - u32 val; - u16 id; - int i; - - val = ar8xxx_read(priv, AR8216_REG_CTRL); - if (val == ~0) - return -ENODEV; - - id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); - for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { - u16 t; - - val = ar8xxx_read(priv, AR8216_REG_CTRL); - if (val == ~0) - return -ENODEV; - - t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); - if (t != id) - return -ENODEV; - } - - priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; - priv->chip_rev = (id & AR8216_CTRL_REVISION); - - switch (priv->chip_ver) { - case AR8XXX_VER_AR8216: - priv->chip = &ar8216_chip; - break; - case AR8XXX_VER_AR8236: - priv->chip = &ar8236_chip; - break; - case AR8XXX_VER_AR8316: - priv->chip = &ar8316_chip; - break; - case AR8XXX_VER_AR8327: - priv->chip = &ar8327_chip; - break; - case AR8XXX_VER_AR8337: - priv->chip = &ar8337_chip; - break; - default: - pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", - priv->chip_ver, priv->chip_rev); - - return -ENODEV; - } - - return 0; -} - -static void -ar8xxx_mib_work_func(struct work_struct *work) -{ - struct ar8xxx_priv *priv; - int err; - - priv = container_of(work, struct ar8xxx_priv, mib_work.work); - - mutex_lock(&priv->mib_lock); - - err = ar8xxx_mib_capture(priv); - if (err) - goto next_port; - - ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); - -next_port: - priv->mib_next_port++; - if (priv->mib_next_port >= priv->dev.ports) - priv->mib_next_port = 0; - - mutex_unlock(&priv->mib_lock); - schedule_delayed_work(&priv->mib_work, - msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); -} - -static int -ar8xxx_mib_init(struct ar8xxx_priv *priv) -{ - unsigned int len; - - if (!ar8xxx_has_mib_counters(priv)) - return 0; - - BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); - - len = priv->dev.ports * priv->chip->num_mibs * - sizeof(*priv->mib_stats); - priv->mib_stats = kzalloc(len, GFP_KERNEL); - - if (!priv->mib_stats) - return -ENOMEM; - - return 0; -} - -static void -ar8xxx_mib_start(struct ar8xxx_priv *priv) -{ - if (!ar8xxx_has_mib_counters(priv)) - return; - - schedule_delayed_work(&priv->mib_work, - msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); -} - -static void -ar8xxx_mib_stop(struct ar8xxx_priv *priv) -{ - if (!ar8xxx_has_mib_counters(priv)) - return; - - cancel_delayed_work_sync(&priv->mib_work); -} - -static struct ar8xxx_priv * -ar8xxx_create(void) -{ - struct ar8xxx_priv *priv; - - priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); - if (priv == NULL) - return NULL; - - mutex_init(&priv->reg_mutex); - mutex_init(&priv->mib_lock); - INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); - - return priv; -} - -static void -ar8xxx_free(struct ar8xxx_priv *priv) -{ - if (priv->chip && priv->chip->cleanup) - priv->chip->cleanup(priv); - - kfree(priv->chip_data); - kfree(priv->mib_stats); - kfree(priv); -} - -static int -ar8xxx_probe_switch(struct ar8xxx_priv *priv) -{ - const struct ar8xxx_chip *chip; - struct switch_dev *swdev; - int ret; - - ret = ar8xxx_id_chip(priv); - if (ret) - return ret; - - chip = priv->chip; - - swdev = &priv->dev; - swdev->cpu_port = AR8216_PORT_CPU; - swdev->name = chip->name; - swdev->vlans = chip->vlans; - swdev->ports = chip->ports; - swdev->ops = chip->swops; - - ret = ar8xxx_mib_init(priv); - if (ret) - return ret; - - return 0; -} - -static int -ar8xxx_start(struct ar8xxx_priv *priv) -{ - int ret; - - priv->init = true; - - ret = priv->chip->hw_init(priv); - if (ret) - return ret; - - ret = ar8xxx_sw_reset_switch(&priv->dev); - if (ret) - return ret; - - priv->init = false; - - ar8xxx_mib_start(priv); - - return 0; -} - -static int -ar8xxx_phy_config_init(struct phy_device *phydev) -{ - struct ar8xxx_priv *priv = phydev->priv; - struct net_device *dev = phydev->attached_dev; - int ret; - - if (WARN_ON(!priv)) - return -ENODEV; - - if (priv->chip->config_at_probe) - return ar8xxx_phy_check_aneg(phydev); - - priv->phy = phydev; - - if (phydev->mdio.addr != 0) { - if (chip_is_ar8316(priv)) { - /* switch device has been initialized, reinit */ - priv->dev.ports = (AR8216_NUM_PORTS - 1); - priv->initialized = false; - priv->port4_phy = true; - ar8316_hw_init(priv); - return 0; - } - - return 0; - } - - ret = ar8xxx_start(priv); - if (ret) - return ret; - - /* VID fixup only needed on ar8216 */ - if (chip_is_ar8216(priv)) { - dev->phy_ptr = priv; - dev->priv_flags |= IFF_NO_IP_ALIGN; - dev->eth_mangle_rx = ar8216_mangle_rx; - dev->eth_mangle_tx = ar8216_mangle_tx; - } - - return 0; -} - -static bool -ar8xxx_check_link_states(struct ar8xxx_priv *priv) -{ - bool link_new, changed = false; - u32 status; - int i; - - mutex_lock(&priv->reg_mutex); - - for (i = 0; i < priv->dev.ports; i++) { - status = priv->chip->read_port_status(priv, i); - link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); - if (link_new == priv->link_up[i]) - continue; - - priv->link_up[i] = link_new; - changed = true; - /* flush ARL entries for this port if it went down*/ - if (!link_new) - priv->chip->atu_flush_port(priv, i); - dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", - i, link_new ? "up" : "down"); - } - - mutex_unlock(&priv->reg_mutex); - - return changed; -} - -static int -ar8xxx_phy_read_status(struct phy_device *phydev) -{ - struct ar8xxx_priv *priv = phydev->priv; - struct switch_port_link link; - - /* check for switch port link changes */ - if (phydev->state == PHY_CHANGELINK) - ar8xxx_check_link_states(priv); - - if (phydev->mdio.addr != 0) - return genphy_read_status(phydev); - - ar8216_read_port_link(priv, phydev->mdio.addr, &link); - phydev->link = !!link.link; - if (!phydev->link) - return 0; - - switch (link.speed) { - case SWITCH_PORT_SPEED_10: - phydev->speed = SPEED_10; - break; - case SWITCH_PORT_SPEED_100: - phydev->speed = SPEED_100; - break; - case SWITCH_PORT_SPEED_1000: - phydev->speed = SPEED_1000; - break; - default: - phydev->speed = 0; - } - phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; - - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - - return 0; -} - -static int -ar8xxx_phy_config_aneg(struct phy_device *phydev) -{ - if (phydev->mdio.addr == 0) - return 0; - - return genphy_config_aneg(phydev); -} - -static const u32 ar8xxx_phy_ids[] = { - 0x004dd033, - 0x004dd034, /* AR8327 */ - 0x004dd036, /* AR8337 */ - 0x004dd041, - 0x004dd042, - 0x004dd043, /* AR8236 */ -}; - -static bool -ar8xxx_phy_match(u32 phy_id) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) - if (phy_id == ar8xxx_phy_ids[i]) - return true; - - return false; -} - -static bool -ar8xxx_is_possible(struct mii_bus *bus) -{ - unsigned int i, found_phys = 0; - - for (i = 0; i < 5; i++) { - u32 phy_id; - - phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; - phy_id |= mdiobus_read(bus, i, MII_PHYSID2); - if (ar8xxx_phy_match(phy_id)) { - found_phys++; - } else if (phy_id) { - pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", - dev_name(&bus->dev), i, phy_id); - } - } - return !!found_phys; -} - -static int -ar8xxx_phy_probe(struct phy_device *phydev) -{ - struct ar8xxx_priv *priv; - struct switch_dev *swdev; - int ret; - - /* skip PHYs at unused adresses */ - if (phydev->mdio.addr != 0 && phydev->mdio.addr != 4) - return -ENODEV; - - if (!ar8xxx_is_possible(phydev->mdio.bus)) - return -ENODEV; - - mutex_lock(&ar8xxx_dev_list_lock); - list_for_each_entry(priv, &ar8xxx_dev_list, list) - if (priv->mii_bus == phydev->mdio.bus) - goto found; - - priv = ar8xxx_create(); - if (priv == NULL) { - ret = -ENOMEM; - goto unlock; - } - - priv->mii_bus = phydev->mdio.bus; - - ret = ar8xxx_probe_switch(priv); - if (ret) - goto free_priv; - - swdev = &priv->dev; - swdev->alias = dev_name(&priv->mii_bus->dev); - ret = register_switch(swdev, NULL); - if (ret) - goto free_priv; - - pr_info("%s: %s rev. %u switch registered on %s\n", - swdev->devname, swdev->name, priv->chip_rev, - dev_name(&priv->mii_bus->dev)); - - list_add(&priv->list, &ar8xxx_dev_list); - -found: - priv->use_count++; - - if (phydev->mdio.addr == 0) { - if (ar8xxx_has_gige(priv)) { - phydev->supported = SUPPORTED_1000baseT_Full; - phydev->advertising = ADVERTISED_1000baseT_Full; - } else { - phydev->supported = SUPPORTED_100baseT_Full; - phydev->advertising = ADVERTISED_100baseT_Full; - } - - if (priv->chip->config_at_probe) { - priv->phy = phydev; - - ret = ar8xxx_start(priv); - if (ret) - goto err_unregister_switch; - } - } else { - if (ar8xxx_has_gige(priv)) { - phydev->supported |= SUPPORTED_1000baseT_Full; - phydev->advertising |= ADVERTISED_1000baseT_Full; - } - } - - phydev->priv = priv; - - mutex_unlock(&ar8xxx_dev_list_lock); - - return 0; - -err_unregister_switch: - if (--priv->use_count) - goto unlock; - - unregister_switch(&priv->dev); - -free_priv: - ar8xxx_free(priv); -unlock: - mutex_unlock(&ar8xxx_dev_list_lock); - return ret; -} - -static void -ar8xxx_phy_detach(struct phy_device *phydev) -{ - struct net_device *dev = phydev->attached_dev; - - if (!dev) - return; - - dev->phy_ptr = NULL; - dev->priv_flags &= ~IFF_NO_IP_ALIGN; - dev->eth_mangle_rx = NULL; - dev->eth_mangle_tx = NULL; -} - -static void -ar8xxx_phy_remove(struct phy_device *phydev) -{ - struct ar8xxx_priv *priv = phydev->priv; - - if (WARN_ON(!priv)) - return; - - phydev->priv = NULL; - - mutex_lock(&ar8xxx_dev_list_lock); - - if (--priv->use_count > 0) { - mutex_unlock(&ar8xxx_dev_list_lock); - return; - } - - list_del(&priv->list); - mutex_unlock(&ar8xxx_dev_list_lock); - - unregister_switch(&priv->dev); - ar8xxx_mib_stop(priv); - ar8xxx_free(priv); -} - -static int -ar8xxx_phy_soft_reset(struct phy_device *phydev) -{ - /* we don't need an extra reset */ - return 0; -} - -static struct phy_driver ar8xxx_phy_driver[] = { - { - .phy_id = 0x004d0000, - .name = "Atheros AR8216/AR8236/AR8316", - .phy_id_mask = 0xffff0000, - .features = PHY_BASIC_FEATURES, - .probe = ar8xxx_phy_probe, - .remove = ar8xxx_phy_remove, - .detach = ar8xxx_phy_detach, - .config_init = ar8xxx_phy_config_init, - .config_aneg = ar8xxx_phy_config_aneg, - .read_status = ar8xxx_phy_read_status, - .soft_reset = ar8xxx_phy_soft_reset, - } -}; - -module_phy_driver(ar8xxx_phy_driver); -MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.h b/target/linux/generic/files/drivers/net/phy/ar8216.h deleted file mode 100644 index ba0e0ddccd..0000000000 --- a/target/linux/generic/files/drivers/net/phy/ar8216.h +++ /dev/null @@ -1,644 +0,0 @@ -/* - * ar8216.h: AR8216 switch driver - * - * Copyright (C) 2009 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef __AR8216_H -#define __AR8216_H - -#define BITS(_s, _n) (((1UL << (_n)) - 1) << _s) - -#define AR8XXX_CAP_GIGE BIT(0) -#define AR8XXX_CAP_MIB_COUNTERS BIT(1) - -#define AR8XXX_NUM_PHYS 5 -#define AR8216_PORT_CPU 0 -#define AR8216_NUM_PORTS 6 -#define AR8216_NUM_VLANS 16 -#define AR8316_NUM_VLANS 4096 - -/* size of the vlan table */ -#define AR8X16_MAX_VLANS 128 -#define AR8X16_PROBE_RETRIES 10 -#define AR8X16_MAX_PORTS 8 - -#define AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS 7 -#define AR8XXX_DEFAULT_ARL_AGE_TIME 300 - -/* Atheros specific MII registers */ -#define MII_ATH_MMD_ADDR 0x0d -#define MII_ATH_MMD_DATA 0x0e -#define MII_ATH_DBG_ADDR 0x1d -#define MII_ATH_DBG_DATA 0x1e - -#define AR8216_REG_CTRL 0x0000 -#define AR8216_CTRL_REVISION BITS(0, 8) -#define AR8216_CTRL_REVISION_S 0 -#define AR8216_CTRL_VERSION BITS(8, 8) -#define AR8216_CTRL_VERSION_S 8 -#define AR8216_CTRL_RESET BIT(31) - -#define AR8216_REG_FLOOD_MASK 0x002C -#define AR8216_FM_UNI_DEST_PORTS BITS(0, 6) -#define AR8216_FM_MULTI_DEST_PORTS BITS(16, 6) -#define AR8236_FM_CPU_BROADCAST_EN BIT(26) -#define AR8236_FM_CPU_BCAST_FWD_EN BIT(25) - -#define AR8216_REG_GLOBAL_CTRL 0x0030 -#define AR8216_GCTRL_MTU BITS(0, 11) -#define AR8236_GCTRL_MTU BITS(0, 14) -#define AR8316_GCTRL_MTU BITS(0, 14) - -#define AR8216_REG_VTU 0x0040 -#define AR8216_VTU_OP BITS(0, 3) -#define AR8216_VTU_OP_NOOP 0x0 -#define AR8216_VTU_OP_FLUSH 0x1 -#define AR8216_VTU_OP_LOAD 0x2 -#define AR8216_VTU_OP_PURGE 0x3 -#define AR8216_VTU_OP_REMOVE_PORT 0x4 -#define AR8216_VTU_ACTIVE BIT(3) -#define AR8216_VTU_FULL BIT(4) -#define AR8216_VTU_PORT BITS(8, 4) -#define AR8216_VTU_PORT_S 8 -#define AR8216_VTU_VID BITS(16, 12) -#define AR8216_VTU_VID_S 16 -#define AR8216_VTU_PRIO BITS(28, 3) -#define AR8216_VTU_PRIO_S 28 -#define AR8216_VTU_PRIO_EN BIT(31) - -#define AR8216_REG_VTU_DATA 0x0044 -#define AR8216_VTUDATA_MEMBER BITS(0, 10) -#define AR8236_VTUDATA_MEMBER BITS(0, 7) -#define AR8216_VTUDATA_VALID BIT(11) - -#define AR8216_REG_ATU_FUNC0 0x0050 -#define AR8216_ATU_OP BITS(0, 3) -#define AR8216_ATU_OP_NOOP 0x0 -#define AR8216_ATU_OP_FLUSH 0x1 -#define AR8216_ATU_OP_LOAD 0x2 -#define AR8216_ATU_OP_PURGE 0x3 -#define AR8216_ATU_OP_FLUSH_UNLOCKED 0x4 -#define AR8216_ATU_OP_FLUSH_PORT 0x5 -#define AR8216_ATU_OP_GET_NEXT 0x6 -#define AR8216_ATU_ACTIVE BIT(3) -#define AR8216_ATU_PORT_NUM BITS(8, 4) -#define AR8216_ATU_PORT_NUM_S 8 -#define AR8216_ATU_FULL_VIO BIT(12) -#define AR8216_ATU_ADDR5 BITS(16, 8) -#define AR8216_ATU_ADDR5_S 16 -#define AR8216_ATU_ADDR4 BITS(24, 8) -#define AR8216_ATU_ADDR4_S 24 - -#define AR8216_REG_ATU_FUNC1 0x0054 -#define AR8216_ATU_ADDR3 BITS(0, 8) -#define AR8216_ATU_ADDR3_S 0 -#define AR8216_ATU_ADDR2 BITS(8, 8) -#define AR8216_ATU_ADDR2_S 8 -#define AR8216_ATU_ADDR1 BITS(16, 8) -#define AR8216_ATU_ADDR1_S 16 -#define AR8216_ATU_ADDR0 BITS(24, 8) -#define AR8216_ATU_ADDR0_S 24 - -#define AR8216_REG_ATU_FUNC2 0x0058 -#define AR8216_ATU_PORTS BITS(0, 6) -#define AR8216_ATU_PORT0 BIT(0) -#define AR8216_ATU_PORT1 BIT(1) -#define AR8216_ATU_PORT2 BIT(2) -#define AR8216_ATU_PORT3 BIT(3) -#define AR8216_ATU_PORT4 BIT(4) -#define AR8216_ATU_PORT5 BIT(5) -#define AR8216_ATU_STATUS BITS(16, 4) -#define AR8216_ATU_STATUS_S 16 - -#define AR8216_REG_ATU_CTRL 0x005C -#define AR8216_ATU_CTRL_AGE_EN BIT(17) -#define AR8216_ATU_CTRL_AGE_TIME BITS(0, 16) -#define AR8216_ATU_CTRL_AGE_TIME_S 0 -#define AR8236_ATU_CTRL_RES BIT(20) - -#define AR8216_REG_MIB_FUNC 0x0080 -#define AR8216_MIB_TIMER BITS(0, 16) -#define AR8216_MIB_AT_HALF_EN BIT(16) -#define AR8216_MIB_BUSY BIT(17) -#define AR8216_MIB_FUNC BITS(24, 3) -#define AR8216_MIB_FUNC_S 24 -#define AR8216_MIB_FUNC_NO_OP 0x0 -#define AR8216_MIB_FUNC_FLUSH 0x1 -#define AR8216_MIB_FUNC_CAPTURE 0x3 -#define AR8236_MIB_EN BIT(30) - -#define AR8216_REG_GLOBAL_CPUPORT 0x0078 -#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT BITS(4, 4) -#define AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S 4 - -#define AR8216_PORT_OFFSET(_i) (0x0100 * (_i + 1)) -#define AR8216_REG_PORT_STATUS(_i) (AR8216_PORT_OFFSET(_i) + 0x0000) -#define AR8216_PORT_STATUS_SPEED BITS(0,2) -#define AR8216_PORT_STATUS_SPEED_S 0 -#define AR8216_PORT_STATUS_TXMAC BIT(2) -#define AR8216_PORT_STATUS_RXMAC BIT(3) -#define AR8216_PORT_STATUS_TXFLOW BIT(4) -#define AR8216_PORT_STATUS_RXFLOW BIT(5) -#define AR8216_PORT_STATUS_DUPLEX BIT(6) -#define AR8216_PORT_STATUS_LINK_UP BIT(8) -#define AR8216_PORT_STATUS_LINK_AUTO BIT(9) -#define AR8216_PORT_STATUS_LINK_PAUSE BIT(10) -#define AR8216_PORT_STATUS_FLOW_CONTROL BIT(12) - -#define AR8216_REG_PORT_CTRL(_i) (AR8216_PORT_OFFSET(_i) + 0x0004) - -/* port forwarding state */ -#define AR8216_PORT_CTRL_STATE BITS(0, 3) -#define AR8216_PORT_CTRL_STATE_S 0 - -#define AR8216_PORT_CTRL_LEARN_LOCK BIT(7) - -/* egress 802.1q mode */ -#define AR8216_PORT_CTRL_VLAN_MODE BITS(8, 2) -#define AR8216_PORT_CTRL_VLAN_MODE_S 8 - -#define AR8216_PORT_CTRL_IGMP_SNOOP BIT(10) -#define AR8216_PORT_CTRL_HEADER BIT(11) -#define AR8216_PORT_CTRL_MAC_LOOP BIT(12) -#define AR8216_PORT_CTRL_SINGLE_VLAN BIT(13) -#define AR8216_PORT_CTRL_LEARN BIT(14) -#define AR8216_PORT_CTRL_MIRROR_TX BIT(16) -#define AR8216_PORT_CTRL_MIRROR_RX BIT(17) - -#define AR8216_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET(_i) + 0x0008) - -#define AR8216_PORT_VLAN_DEFAULT_ID BITS(0, 12) -#define AR8216_PORT_VLAN_DEFAULT_ID_S 0 - -#define AR8216_PORT_VLAN_DEST_PORTS BITS(16, 9) -#define AR8216_PORT_VLAN_DEST_PORTS_S 16 - -/* bit0 added to the priority field of egress frames */ -#define AR8216_PORT_VLAN_TX_PRIO BIT(27) - -/* port default priority */ -#define AR8216_PORT_VLAN_PRIORITY BITS(28, 2) -#define AR8216_PORT_VLAN_PRIORITY_S 28 - -/* ingress 802.1q mode */ -#define AR8216_PORT_VLAN_MODE BITS(30, 2) -#define AR8216_PORT_VLAN_MODE_S 30 - -#define AR8216_REG_PORT_RATE(_i) (AR8216_PORT_OFFSET(_i) + 0x000c) -#define AR8216_REG_PORT_PRIO(_i) (AR8216_PORT_OFFSET(_i) + 0x0010) - -#define AR8216_STATS_RXBROAD 0x00 -#define AR8216_STATS_RXPAUSE 0x04 -#define AR8216_STATS_RXMULTI 0x08 -#define AR8216_STATS_RXFCSERR 0x0c -#define AR8216_STATS_RXALIGNERR 0x10 -#define AR8216_STATS_RXRUNT 0x14 -#define AR8216_STATS_RXFRAGMENT 0x18 -#define AR8216_STATS_RX64BYTE 0x1c -#define AR8216_STATS_RX128BYTE 0x20 -#define AR8216_STATS_RX256BYTE 0x24 -#define AR8216_STATS_RX512BYTE 0x28 -#define AR8216_STATS_RX1024BYTE 0x2c -#define AR8216_STATS_RXMAXBYTE 0x30 -#define AR8216_STATS_RXTOOLONG 0x34 -#define AR8216_STATS_RXGOODBYTE 0x38 -#define AR8216_STATS_RXBADBYTE 0x40 -#define AR8216_STATS_RXOVERFLOW 0x48 -#define AR8216_STATS_FILTERED 0x4c -#define AR8216_STATS_TXBROAD 0x50 -#define AR8216_STATS_TXPAUSE 0x54 -#define AR8216_STATS_TXMULTI 0x58 -#define AR8216_STATS_TXUNDERRUN 0x5c -#define AR8216_STATS_TX64BYTE 0x60 -#define AR8216_STATS_TX128BYTE 0x64 -#define AR8216_STATS_TX256BYTE 0x68 -#define AR8216_STATS_TX512BYTE 0x6c -#define AR8216_STATS_TX1024BYTE 0x70 -#define AR8216_STATS_TXMAXBYTE 0x74 -#define AR8216_STATS_TXOVERSIZE 0x78 -#define AR8216_STATS_TXBYTE 0x7c -#define AR8216_STATS_TXCOLLISION 0x84 -#define AR8216_STATS_TXABORTCOL 0x88 -#define AR8216_STATS_TXMULTICOL 0x8c -#define AR8216_STATS_TXSINGLECOL 0x90 -#define AR8216_STATS_TXEXCDEFER 0x94 -#define AR8216_STATS_TXDEFER 0x98 -#define AR8216_STATS_TXLATECOL 0x9c - -#define AR8236_REG_PORT_VLAN(_i) (AR8216_PORT_OFFSET((_i)) + 0x0008) -#define AR8236_PORT_VLAN_DEFAULT_ID BITS(16, 12) -#define AR8236_PORT_VLAN_DEFAULT_ID_S 16 -#define AR8236_PORT_VLAN_PRIORITY BITS(29, 3) -#define AR8236_PORT_VLAN_PRIORITY_S 28 - -#define AR8236_REG_PORT_VLAN2(_i) (AR8216_PORT_OFFSET((_i)) + 0x000c) -#define AR8236_PORT_VLAN2_MEMBER BITS(16, 7) -#define AR8236_PORT_VLAN2_MEMBER_S 16 -#define AR8236_PORT_VLAN2_TX_PRIO BIT(23) -#define AR8236_PORT_VLAN2_VLAN_MODE BITS(30, 2) -#define AR8236_PORT_VLAN2_VLAN_MODE_S 30 - -#define AR8236_STATS_RXBROAD 0x00 -#define AR8236_STATS_RXPAUSE 0x04 -#define AR8236_STATS_RXMULTI 0x08 -#define AR8236_STATS_RXFCSERR 0x0c -#define AR8236_STATS_RXALIGNERR 0x10 -#define AR8236_STATS_RXRUNT 0x14 -#define AR8236_STATS_RXFRAGMENT 0x18 -#define AR8236_STATS_RX64BYTE 0x1c -#define AR8236_STATS_RX128BYTE 0x20 -#define AR8236_STATS_RX256BYTE 0x24 -#define AR8236_STATS_RX512BYTE 0x28 -#define AR8236_STATS_RX1024BYTE 0x2c -#define AR8236_STATS_RX1518BYTE 0x30 -#define AR8236_STATS_RXMAXBYTE 0x34 -#define AR8236_STATS_RXTOOLONG 0x38 -#define AR8236_STATS_RXGOODBYTE 0x3c -#define AR8236_STATS_RXBADBYTE 0x44 -#define AR8236_STATS_RXOVERFLOW 0x4c -#define AR8236_STATS_FILTERED 0x50 -#define AR8236_STATS_TXBROAD 0x54 -#define AR8236_STATS_TXPAUSE 0x58 -#define AR8236_STATS_TXMULTI 0x5c -#define AR8236_STATS_TXUNDERRUN 0x60 -#define AR8236_STATS_TX64BYTE 0x64 -#define AR8236_STATS_TX128BYTE 0x68 -#define AR8236_STATS_TX256BYTE 0x6c -#define AR8236_STATS_TX512BYTE 0x70 -#define AR8236_STATS_TX1024BYTE 0x74 -#define AR8236_STATS_TX1518BYTE 0x78 -#define AR8236_STATS_TXMAXBYTE 0x7c -#define AR8236_STATS_TXOVERSIZE 0x80 -#define AR8236_STATS_TXBYTE 0x84 -#define AR8236_STATS_TXCOLLISION 0x8c -#define AR8236_STATS_TXABORTCOL 0x90 -#define AR8236_STATS_TXMULTICOL 0x94 -#define AR8236_STATS_TXSINGLECOL 0x98 -#define AR8236_STATS_TXEXCDEFER 0x9c -#define AR8236_STATS_TXDEFER 0xa0 -#define AR8236_STATS_TXLATECOL 0xa4 - -#define AR8316_REG_POSTRIP 0x0008 -#define AR8316_POSTRIP_MAC0_GMII_EN BIT(0) -#define AR8316_POSTRIP_MAC0_RGMII_EN BIT(1) -#define AR8316_POSTRIP_PHY4_GMII_EN BIT(2) -#define AR8316_POSTRIP_PHY4_RGMII_EN BIT(3) -#define AR8316_POSTRIP_MAC0_MAC_MODE BIT(4) -#define AR8316_POSTRIP_RTL_MODE BIT(5) -#define AR8316_POSTRIP_RGMII_RXCLK_DELAY_EN BIT(6) -#define AR8316_POSTRIP_RGMII_TXCLK_DELAY_EN BIT(7) -#define AR8316_POSTRIP_SERDES_EN BIT(8) -#define AR8316_POSTRIP_SEL_ANA_RST BIT(9) -#define AR8316_POSTRIP_GATE_25M_EN BIT(10) -#define AR8316_POSTRIP_SEL_CLK25M BIT(11) -#define AR8316_POSTRIP_HIB_PULSE_HW BIT(12) -#define AR8316_POSTRIP_DBG_MODE_I BIT(13) -#define AR8316_POSTRIP_MAC5_MAC_MODE BIT(14) -#define AR8316_POSTRIP_MAC5_PHY_MODE BIT(15) -#define AR8316_POSTRIP_POWER_DOWN_HW BIT(16) -#define AR8316_POSTRIP_LPW_STATE_EN BIT(17) -#define AR8316_POSTRIP_MAN_EN BIT(18) -#define AR8316_POSTRIP_PHY_PLL_ON BIT(19) -#define AR8316_POSTRIP_LPW_EXIT BIT(20) -#define AR8316_POSTRIP_TXDELAY_S0 BIT(21) -#define AR8316_POSTRIP_TXDELAY_S1 BIT(22) -#define AR8316_POSTRIP_RXDELAY_S0 BIT(23) -#define AR8316_POSTRIP_LED_OPEN_EN BIT(24) -#define AR8316_POSTRIP_SPI_EN BIT(25) -#define AR8316_POSTRIP_RXDELAY_S1 BIT(26) -#define AR8316_POSTRIP_POWER_ON_SEL BIT(31) - -/* port speed */ -enum { - AR8216_PORT_SPEED_10M = 0, - AR8216_PORT_SPEED_100M = 1, - AR8216_PORT_SPEED_1000M = 2, - AR8216_PORT_SPEED_ERR = 3, -}; - -/* ingress 802.1q mode */ -enum { - AR8216_IN_PORT_ONLY = 0, - AR8216_IN_PORT_FALLBACK = 1, - AR8216_IN_VLAN_ONLY = 2, - AR8216_IN_SECURE = 3 -}; - -/* egress 802.1q mode */ -enum { - AR8216_OUT_KEEP = 0, - AR8216_OUT_STRIP_VLAN = 1, - AR8216_OUT_ADD_VLAN = 2 -}; - -/* port forwarding state */ -enum { - AR8216_PORT_STATE_DISABLED = 0, - AR8216_PORT_STATE_BLOCK = 1, - AR8216_PORT_STATE_LISTEN = 2, - AR8216_PORT_STATE_LEARN = 3, - AR8216_PORT_STATE_FORWARD = 4 -}; - -enum { - AR8XXX_VER_AR8216 = 0x01, - AR8XXX_VER_AR8236 = 0x03, - AR8XXX_VER_AR8316 = 0x10, - AR8XXX_VER_AR8327 = 0x12, - AR8XXX_VER_AR8337 = 0x13, -}; - -#define AR8XXX_NUM_ARL_RECORDS 100 - -enum arl_op { - AR8XXX_ARL_INITIALIZE, - AR8XXX_ARL_GET_NEXT -}; - -struct arl_entry { - u8 port; - u8 mac[6]; -}; - -struct ar8xxx_priv; - -struct ar8xxx_mib_desc { - unsigned int size; - unsigned int offset; - const char *name; -}; - -struct ar8xxx_chip { - unsigned long caps; - bool config_at_probe; - bool mii_lo_first; - - /* parameters to calculate REG_PORT_STATS_BASE */ - unsigned reg_port_stats_start; - unsigned reg_port_stats_length; - - unsigned reg_arl_ctrl; - - int (*hw_init)(struct ar8xxx_priv *priv); - void (*cleanup)(struct ar8xxx_priv *priv); - - const char *name; - int vlans; - int ports; - const struct switch_dev_ops *swops; - - void (*init_globals)(struct ar8xxx_priv *priv); - void (*init_port)(struct ar8xxx_priv *priv, int port); - void (*setup_port)(struct ar8xxx_priv *priv, int port, u32 members); - u32 (*read_port_status)(struct ar8xxx_priv *priv, int port); - u32 (*read_port_eee_status)(struct ar8xxx_priv *priv, int port); - int (*atu_flush)(struct ar8xxx_priv *priv); - int (*atu_flush_port)(struct ar8xxx_priv *priv, int port); - void (*vtu_flush)(struct ar8xxx_priv *priv); - void (*vtu_load_vlan)(struct ar8xxx_priv *priv, u32 vid, u32 port_mask); - void (*phy_fixup)(struct ar8xxx_priv *priv, int phy); - void (*set_mirror_regs)(struct ar8xxx_priv *priv); - void (*get_arl_entry)(struct ar8xxx_priv *priv, struct arl_entry *a, - u32 *status, enum arl_op op); - int (*sw_hw_apply)(struct switch_dev *dev); - - const struct ar8xxx_mib_desc *mib_decs; - unsigned num_mibs; - unsigned mib_func; -}; - -struct ar8xxx_priv { - struct switch_dev dev; - struct mii_bus *mii_bus; - struct phy_device *phy; - - int (*get_port_link)(unsigned port); - - const struct net_device_ops *ndo_old; - struct net_device_ops ndo; - struct mutex reg_mutex; - u8 chip_ver; - u8 chip_rev; - const struct ar8xxx_chip *chip; - void *chip_data; - bool initialized; - bool port4_phy; - char buf[2048]; - struct arl_entry arl_table[AR8XXX_NUM_ARL_RECORDS]; - char arl_buf[AR8XXX_NUM_ARL_RECORDS * 32 + 256]; - bool link_up[AR8X16_MAX_PORTS]; - - bool init; - - struct mutex mib_lock; - struct delayed_work mib_work; - int mib_next_port; - u64 *mib_stats; - - struct list_head list; - unsigned int use_count; - - /* all fields below are cleared on reset */ - bool vlan; - u16 vlan_id[AR8X16_MAX_VLANS]; - u8 vlan_table[AR8X16_MAX_VLANS]; - u8 vlan_tagged; - u16 pvid[AR8X16_MAX_PORTS]; - int arl_age_time; - - /* mirroring */ - bool mirror_rx; - bool mirror_tx; - int source_port; - int monitor_port; - u8 port_vlan_prio[AR8X16_MAX_PORTS]; -}; - -u32 -ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum); -void -ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val); -u32 -ar8xxx_read(struct ar8xxx_priv *priv, int reg); -void -ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val); -u32 -ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); - -void -ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, - u16 dbg_addr, u16 dbg_data); -void -ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data); -u16 -ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg); -void -ar8xxx_phy_init(struct ar8xxx_priv *priv); -int -ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan); -int -ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan); -int -ar8xxx_sw_hw_apply(struct switch_dev *dev); -int -ar8xxx_sw_reset_switch(struct switch_dev *dev); -int -ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, - struct switch_port_link *link); -int -ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_port_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_get_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int -ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val); - -static inline struct ar8xxx_priv * -swdev_to_ar8xxx(struct switch_dev *swdev) -{ - return container_of(swdev, struct ar8xxx_priv, dev); -} - -static inline bool ar8xxx_has_gige(struct ar8xxx_priv *priv) -{ - return priv->chip->caps & AR8XXX_CAP_GIGE; -} - -static inline bool ar8xxx_has_mib_counters(struct ar8xxx_priv *priv) -{ - return priv->chip->caps & AR8XXX_CAP_MIB_COUNTERS; -} - -static inline bool chip_is_ar8216(struct ar8xxx_priv *priv) -{ - return priv->chip_ver == AR8XXX_VER_AR8216; -} - -static inline bool chip_is_ar8236(struct ar8xxx_priv *priv) -{ - return priv->chip_ver == AR8XXX_VER_AR8236; -} - -static inline bool chip_is_ar8316(struct ar8xxx_priv *priv) -{ - return priv->chip_ver == AR8XXX_VER_AR8316; -} - -static inline bool chip_is_ar8327(struct ar8xxx_priv *priv) -{ - return priv->chip_ver == AR8XXX_VER_AR8327; -} - -static inline bool chip_is_ar8337(struct ar8xxx_priv *priv) -{ - return priv->chip_ver == AR8XXX_VER_AR8337; -} - -static inline void -ar8xxx_reg_set(struct ar8xxx_priv *priv, int reg, u32 val) -{ - ar8xxx_rmw(priv, reg, 0, val); -} - -static inline void -ar8xxx_reg_clear(struct ar8xxx_priv *priv, int reg, u32 val) -{ - ar8xxx_rmw(priv, reg, val, 0); -} - -static inline void -split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) -{ - regaddr >>= 1; - *r1 = regaddr & 0x1e; - - regaddr >>= 5; - *r2 = regaddr & 0x7; - - regaddr >>= 3; - *page = regaddr & 0x1ff; -} - -static inline void -wait_for_page_switch(void) -{ - udelay(5); -} - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.c b/target/linux/generic/files/drivers/net/phy/ar8327.c deleted file mode 100644 index 74f0a08d76..0000000000 --- a/target/linux/generic/files/drivers/net/phy/ar8327.c +++ /dev/null @@ -1,1503 +0,0 @@ -/* - * ar8327.c: AR8216 switch driver - * - * Copyright (C) 2009 Felix Fietkau - * Copyright (C) 2011-2012 Gabor Juhos - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ar8216.h" -#include "ar8327.h" - -extern const struct ar8xxx_mib_desc ar8236_mibs[39]; -extern const struct switch_attr ar8xxx_sw_attr_vlan[1]; - -static u32 -ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg) -{ - u32 t; - - if (!cfg) - return 0; - - t = 0; - switch (cfg->mode) { - case AR8327_PAD_NC: - break; - - case AR8327_PAD_MAC2MAC_MII: - t = AR8327_PAD_MAC_MII_EN; - if (cfg->rxclk_sel) - t |= AR8327_PAD_MAC_MII_RXCLK_SEL; - if (cfg->txclk_sel) - t |= AR8327_PAD_MAC_MII_TXCLK_SEL; - break; - - case AR8327_PAD_MAC2MAC_GMII: - t = AR8327_PAD_MAC_GMII_EN; - if (cfg->rxclk_sel) - t |= AR8327_PAD_MAC_GMII_RXCLK_SEL; - if (cfg->txclk_sel) - t |= AR8327_PAD_MAC_GMII_TXCLK_SEL; - break; - - case AR8327_PAD_MAC_SGMII: - t = AR8327_PAD_SGMII_EN; - - /* - * WAR for the QUalcomm Atheros AP136 board. - * It seems that RGMII TX/RX delay settings needs to be - * applied for SGMII mode as well, The ethernet is not - * reliable without this. - */ - t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; - t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; - if (cfg->rxclk_delay_en) - t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; - if (cfg->txclk_delay_en) - t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; - - if (cfg->sgmii_delay_en) - t |= AR8327_PAD_SGMII_DELAY_EN; - - break; - - case AR8327_PAD_MAC2PHY_MII: - t = AR8327_PAD_PHY_MII_EN; - if (cfg->rxclk_sel) - t |= AR8327_PAD_PHY_MII_RXCLK_SEL; - if (cfg->txclk_sel) - t |= AR8327_PAD_PHY_MII_TXCLK_SEL; - break; - - case AR8327_PAD_MAC2PHY_GMII: - t = AR8327_PAD_PHY_GMII_EN; - if (cfg->pipe_rxclk_sel) - t |= AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL; - if (cfg->rxclk_sel) - t |= AR8327_PAD_PHY_GMII_RXCLK_SEL; - if (cfg->txclk_sel) - t |= AR8327_PAD_PHY_GMII_TXCLK_SEL; - break; - - case AR8327_PAD_MAC_RGMII: - t = AR8327_PAD_RGMII_EN; - t |= cfg->txclk_delay_sel << AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S; - t |= cfg->rxclk_delay_sel << AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S; - if (cfg->rxclk_delay_en) - t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN; - if (cfg->txclk_delay_en) - t |= AR8327_PAD_RGMII_TXCLK_DELAY_EN; - break; - - case AR8327_PAD_PHY_GMII: - t = AR8327_PAD_PHYX_GMII_EN; - break; - - case AR8327_PAD_PHY_RGMII: - t = AR8327_PAD_PHYX_RGMII_EN; - break; - - case AR8327_PAD_PHY_MII: - t = AR8327_PAD_PHYX_MII_EN; - break; - } - - return t; -} - -static void -ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy) -{ - switch (priv->chip_rev) { - case 1: - /* For 100M waveform */ - ar8xxx_phy_dbg_write(priv, phy, 0, 0x02ea); - /* Turn on Gigabit clock */ - ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x68a0); - break; - - case 2: - ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0); - /* fallthrough */ - case 4: - ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f); - ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860); - ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46); - ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000); - break; - } -} - -static u32 -ar8327_get_port_init_status(struct ar8327_port_cfg *cfg) -{ - u32 t; - - if (!cfg->force_link) - return AR8216_PORT_STATUS_LINK_AUTO; - - t = AR8216_PORT_STATUS_TXMAC | AR8216_PORT_STATUS_RXMAC; - t |= cfg->duplex ? AR8216_PORT_STATUS_DUPLEX : 0; - t |= cfg->rxpause ? AR8216_PORT_STATUS_RXFLOW : 0; - t |= cfg->txpause ? AR8216_PORT_STATUS_TXFLOW : 0; - - switch (cfg->speed) { - case AR8327_PORT_SPEED_10: - t |= AR8216_PORT_SPEED_10M; - break; - case AR8327_PORT_SPEED_100: - t |= AR8216_PORT_SPEED_100M; - break; - case AR8327_PORT_SPEED_1000: - t |= AR8216_PORT_SPEED_1000M; - break; - } - - return t; -} - -#define AR8327_LED_ENTRY(_num, _reg, _shift) \ - [_num] = { .reg = (_reg), .shift = (_shift) } - -static const struct ar8327_led_entry -ar8327_led_map[AR8327_NUM_LEDS] = { - AR8327_LED_ENTRY(AR8327_LED_PHY0_0, 0, 14), - AR8327_LED_ENTRY(AR8327_LED_PHY0_1, 1, 14), - AR8327_LED_ENTRY(AR8327_LED_PHY0_2, 2, 14), - - AR8327_LED_ENTRY(AR8327_LED_PHY1_0, 3, 8), - AR8327_LED_ENTRY(AR8327_LED_PHY1_1, 3, 10), - AR8327_LED_ENTRY(AR8327_LED_PHY1_2, 3, 12), - - AR8327_LED_ENTRY(AR8327_LED_PHY2_0, 3, 14), - AR8327_LED_ENTRY(AR8327_LED_PHY2_1, 3, 16), - AR8327_LED_ENTRY(AR8327_LED_PHY2_2, 3, 18), - - AR8327_LED_ENTRY(AR8327_LED_PHY3_0, 3, 20), - AR8327_LED_ENTRY(AR8327_LED_PHY3_1, 3, 22), - AR8327_LED_ENTRY(AR8327_LED_PHY3_2, 3, 24), - - AR8327_LED_ENTRY(AR8327_LED_PHY4_0, 0, 30), - AR8327_LED_ENTRY(AR8327_LED_PHY4_1, 1, 30), - AR8327_LED_ENTRY(AR8327_LED_PHY4_2, 2, 30), -}; - -static void -ar8327_set_led_pattern(struct ar8xxx_priv *priv, unsigned int led_num, - enum ar8327_led_pattern pattern) -{ - const struct ar8327_led_entry *entry; - - entry = &ar8327_led_map[led_num]; - ar8xxx_rmw(priv, AR8327_REG_LED_CTRL(entry->reg), - (3 << entry->shift), pattern << entry->shift); -} - -static void -ar8327_led_work_func(struct work_struct *work) -{ - struct ar8327_led *aled; - u8 pattern; - - aled = container_of(work, struct ar8327_led, led_work); - - pattern = aled->pattern; - - ar8327_set_led_pattern(aled->sw_priv, aled->led_num, - pattern); -} - -static void -ar8327_led_schedule_change(struct ar8327_led *aled, u8 pattern) -{ - if (aled->pattern == pattern) - return; - - aled->pattern = pattern; - schedule_work(&aled->led_work); -} - -static inline struct ar8327_led * -led_cdev_to_ar8327_led(struct led_classdev *led_cdev) -{ - return container_of(led_cdev, struct ar8327_led, cdev); -} - -static int -ar8327_led_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, - unsigned long *delay_off) -{ - struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); - - if (*delay_on == 0 && *delay_off == 0) { - *delay_on = 125; - *delay_off = 125; - } - - if (*delay_on != 125 || *delay_off != 125) { - /* - * The hardware only supports blinking at 4Hz. Fall back - * to software implementation in other cases. - */ - return -EINVAL; - } - - spin_lock(&aled->lock); - - aled->enable_hw_mode = false; - ar8327_led_schedule_change(aled, AR8327_LED_PATTERN_BLINK); - - spin_unlock(&aled->lock); - - return 0; -} - -static void -ar8327_led_set_brightness(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); - u8 pattern; - bool active; - - active = (brightness != LED_OFF); - active ^= aled->active_low; - - pattern = (active) ? AR8327_LED_PATTERN_ON : - AR8327_LED_PATTERN_OFF; - - spin_lock(&aled->lock); - - aled->enable_hw_mode = false; - ar8327_led_schedule_change(aled, pattern); - - spin_unlock(&aled->lock); -} - -static ssize_t -ar8327_led_enable_hw_mode_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); - ssize_t ret = 0; - - ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode); - - return ret; -} - -static ssize_t -ar8327_led_enable_hw_mode_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev); - u8 pattern; - u8 value; - int ret; - - ret = kstrtou8(buf, 10, &value); - if (ret < 0) - return -EINVAL; - - spin_lock(&aled->lock); - - aled->enable_hw_mode = !!value; - if (aled->enable_hw_mode) - pattern = AR8327_LED_PATTERN_RULE; - else - pattern = AR8327_LED_PATTERN_OFF; - - ar8327_led_schedule_change(aled, pattern); - - spin_unlock(&aled->lock); - - return size; -} - -static DEVICE_ATTR(enable_hw_mode, S_IRUGO | S_IWUSR, - ar8327_led_enable_hw_mode_show, - ar8327_led_enable_hw_mode_store); - -static int -ar8327_led_register(struct ar8327_led *aled) -{ - int ret; - - ret = led_classdev_register(NULL, &aled->cdev); - if (ret < 0) - return ret; - - if (aled->mode == AR8327_LED_MODE_HW) { - ret = device_create_file(aled->cdev.dev, - &dev_attr_enable_hw_mode); - if (ret) - goto err_unregister; - } - - return 0; - -err_unregister: - led_classdev_unregister(&aled->cdev); - return ret; -} - -static void -ar8327_led_unregister(struct ar8327_led *aled) -{ - if (aled->mode == AR8327_LED_MODE_HW) - device_remove_file(aled->cdev.dev, &dev_attr_enable_hw_mode); - - led_classdev_unregister(&aled->cdev); - cancel_work_sync(&aled->led_work); -} - -static int -ar8327_led_create(struct ar8xxx_priv *priv, - const struct ar8327_led_info *led_info) -{ - struct ar8327_data *data = priv->chip_data; - struct ar8327_led *aled; - int ret; - - if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) - return 0; - - if (!led_info->name) - return -EINVAL; - - if (led_info->led_num >= AR8327_NUM_LEDS) - return -EINVAL; - - aled = kzalloc(sizeof(*aled) + strlen(led_info->name) + 1, - GFP_KERNEL); - if (!aled) - return -ENOMEM; - - aled->sw_priv = priv; - aled->led_num = led_info->led_num; - aled->active_low = led_info->active_low; - aled->mode = led_info->mode; - - if (aled->mode == AR8327_LED_MODE_HW) - aled->enable_hw_mode = true; - - aled->name = (char *)(aled + 1); - strcpy(aled->name, led_info->name); - - aled->cdev.name = aled->name; - aled->cdev.brightness_set = ar8327_led_set_brightness; - aled->cdev.blink_set = ar8327_led_blink_set; - aled->cdev.default_trigger = led_info->default_trigger; - - spin_lock_init(&aled->lock); - mutex_init(&aled->mutex); - INIT_WORK(&aled->led_work, ar8327_led_work_func); - - ret = ar8327_led_register(aled); - if (ret) - goto err_free; - - data->leds[data->num_leds++] = aled; - - return 0; - -err_free: - kfree(aled); - return ret; -} - -static void -ar8327_led_destroy(struct ar8327_led *aled) -{ - ar8327_led_unregister(aled); - kfree(aled); -} - -static void -ar8327_leds_init(struct ar8xxx_priv *priv) -{ - struct ar8327_data *data = priv->chip_data; - unsigned i; - - if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) - return; - - for (i = 0; i < data->num_leds; i++) { - struct ar8327_led *aled; - - aled = data->leds[i]; - - if (aled->enable_hw_mode) - aled->pattern = AR8327_LED_PATTERN_RULE; - else - aled->pattern = AR8327_LED_PATTERN_OFF; - - ar8327_set_led_pattern(priv, aled->led_num, aled->pattern); - } -} - -static void -ar8327_leds_cleanup(struct ar8xxx_priv *priv) -{ - struct ar8327_data *data = priv->chip_data; - unsigned i; - - if (!IS_ENABLED(CONFIG_AR8216_PHY_LEDS)) - return; - - for (i = 0; i < data->num_leds; i++) { - struct ar8327_led *aled; - - aled = data->leds[i]; - ar8327_led_destroy(aled); - } - - kfree(data->leds); -} - -static int -ar8327_hw_config_pdata(struct ar8xxx_priv *priv, - struct ar8327_platform_data *pdata) -{ - struct ar8327_led_cfg *led_cfg; - struct ar8327_data *data = priv->chip_data; - u32 pos, new_pos; - u32 t; - - if (!pdata) - return -EINVAL; - - priv->get_port_link = pdata->get_port_link; - - data->port0_status = ar8327_get_port_init_status(&pdata->port0_cfg); - data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg); - - t = ar8327_get_pad_cfg(pdata->pad0_cfg); - if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis) - t |= AR8337_PAD_MAC06_EXCHANGE_EN; - ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t); - - t = ar8327_get_pad_cfg(pdata->pad5_cfg); - ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t); - t = ar8327_get_pad_cfg(pdata->pad6_cfg); - ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t); - - pos = ar8xxx_read(priv, AR8327_REG_POWER_ON_STRIP); - new_pos = pos; - - led_cfg = pdata->led_cfg; - if (led_cfg) { - if (led_cfg->open_drain) - new_pos |= AR8327_POWER_ON_STRIP_LED_OPEN_EN; - else - new_pos &= ~AR8327_POWER_ON_STRIP_LED_OPEN_EN; - - ar8xxx_write(priv, AR8327_REG_LED_CTRL0, led_cfg->led_ctrl0); - ar8xxx_write(priv, AR8327_REG_LED_CTRL1, led_cfg->led_ctrl1); - ar8xxx_write(priv, AR8327_REG_LED_CTRL2, led_cfg->led_ctrl2); - ar8xxx_write(priv, AR8327_REG_LED_CTRL3, led_cfg->led_ctrl3); - - if (new_pos != pos) - new_pos |= AR8327_POWER_ON_STRIP_POWER_ON_SEL; - } - - if (pdata->sgmii_cfg) { - t = pdata->sgmii_cfg->sgmii_ctrl; - if (priv->chip_rev == 1) - t |= AR8327_SGMII_CTRL_EN_PLL | - AR8327_SGMII_CTRL_EN_RX | - AR8327_SGMII_CTRL_EN_TX; - else - t &= ~(AR8327_SGMII_CTRL_EN_PLL | - AR8327_SGMII_CTRL_EN_RX | - AR8327_SGMII_CTRL_EN_TX); - - ar8xxx_write(priv, AR8327_REG_SGMII_CTRL, t); - - if (pdata->sgmii_cfg->serdes_aen) - new_pos &= ~AR8327_POWER_ON_STRIP_SERDES_AEN; - else - new_pos |= AR8327_POWER_ON_STRIP_SERDES_AEN; - } - - ar8xxx_write(priv, AR8327_REG_POWER_ON_STRIP, new_pos); - - if (pdata->leds && pdata->num_leds) { - int i; - - data->leds = kzalloc(pdata->num_leds * sizeof(void *), - GFP_KERNEL); - if (!data->leds) - return -ENOMEM; - - for (i = 0; i < pdata->num_leds; i++) - ar8327_led_create(priv, &pdata->leds[i]); - } - - return 0; -} - -#ifdef CONFIG_OF -static int -ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) -{ - struct ar8327_data *data = priv->chip_data; - const __be32 *paddr; - int len; - int i; - - paddr = of_get_property(np, "qca,ar8327-initvals", &len); - if (!paddr || len < (2 * sizeof(*paddr))) - return -EINVAL; - - len /= sizeof(*paddr); - - for (i = 0; i < len - 1; i += 2) { - u32 reg; - u32 val; - - reg = be32_to_cpup(paddr + i); - val = be32_to_cpup(paddr + i + 1); - - switch (reg) { - case AR8327_REG_PORT_STATUS(0): - data->port0_status = val; - break; - case AR8327_REG_PORT_STATUS(6): - data->port6_status = val; - break; - default: - ar8xxx_write(priv, reg, val); - break; - } - } - - return 0; -} -#else -static inline int -ar8327_hw_config_of(struct ar8xxx_priv *priv, struct device_node *np) -{ - return -EINVAL; -} -#endif - -static int -ar8327_hw_init(struct ar8xxx_priv *priv) -{ - int ret; - - priv->chip_data = kzalloc(sizeof(struct ar8327_data), GFP_KERNEL); - if (!priv->chip_data) - return -ENOMEM; - - if (priv->phy->mdio.dev.of_node) - ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node); - else - ret = ar8327_hw_config_pdata(priv, - priv->phy->mdio.dev.platform_data); - - if (ret) - return ret; - - ar8327_leds_init(priv); - - ar8xxx_phy_init(priv); - - return 0; -} - -static void -ar8327_cleanup(struct ar8xxx_priv *priv) -{ - ar8327_leds_cleanup(priv); -} - -static void -ar8327_init_globals(struct ar8xxx_priv *priv) -{ - struct ar8327_data *data = priv->chip_data; - u32 t; - int i; - - /* enable CPU port and disable mirror port */ - t = AR8327_FWD_CTRL0_CPU_PORT_EN | - AR8327_FWD_CTRL0_MIRROR_PORT; - ar8xxx_write(priv, AR8327_REG_FWD_CTRL0, t); - - /* forward multicast and broadcast frames to CPU */ - t = (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_UC_FLOOD_S) | - (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_MC_FLOOD_S) | - (AR8327_PORTS_ALL << AR8327_FWD_CTRL1_BC_FLOOD_S); - ar8xxx_write(priv, AR8327_REG_FWD_CTRL1, t); - - /* enable jumbo frames */ - ar8xxx_rmw(priv, AR8327_REG_MAX_FRAME_SIZE, - AR8327_MAX_FRAME_SIZE_MTU, 9018 + 8 + 2); - - /* Enable MIB counters */ - ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN, - AR8327_MODULE_EN_MIB); - - /* Disable EEE on all phy's due to stability issues */ - for (i = 0; i < AR8XXX_NUM_PHYS; i++) - data->eee[i] = false; -} - -static void -ar8327_init_port(struct ar8xxx_priv *priv, int port) -{ - struct ar8327_data *data = priv->chip_data; - u32 t; - - if (port == AR8216_PORT_CPU) - t = data->port0_status; - else if (port == 6) - t = data->port6_status; - else - t = AR8216_PORT_STATUS_LINK_AUTO; - - if (port != AR8216_PORT_CPU && port != 6) { - /*hw limitation:if configure mac when there is traffic, - port MAC may work abnormal. Need disable lan&wan mac at fisrt*/ - ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0); - msleep(100); - t |= AR8216_PORT_STATUS_FLOW_CONTROL; - ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); - } else { - ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t); - } - - ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0); - - ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0); - - t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S; - ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); - - t = AR8327_PORT_LOOKUP_LEARN; - t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; - ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); -} - -static u32 -ar8327_read_port_status(struct ar8xxx_priv *priv, int port) -{ - u32 t; - - t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port)); - /* map the flow control autoneg result bits to the flow control bits - * used in forced mode to allow ar8216_read_port_link detect - * flow control properly if autoneg is used - */ - if (t & AR8216_PORT_STATUS_LINK_UP && - t & AR8216_PORT_STATUS_LINK_AUTO) { - t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW); - if (t & AR8327_PORT_STATUS_TXFLOW_AUTO) - t |= AR8216_PORT_STATUS_TXFLOW; - if (t & AR8327_PORT_STATUS_RXFLOW_AUTO) - t |= AR8216_PORT_STATUS_RXFLOW; - } - - return t; -} - -static u32 -ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port) -{ - int phy; - u16 t; - - if (port >= priv->dev.ports) - return 0; - - if (port == 0 || port == 6) - return 0; - - phy = port - 1; - - /* EEE Ability Auto-negotiation Result */ - t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000); - - return mmd_eee_adv_to_ethtool_adv_t(t); -} - -static int -ar8327_atu_flush(struct ar8xxx_priv *priv) -{ - int ret; - - ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, - AR8327_ATU_FUNC_BUSY, 0); - if (!ret) - ar8xxx_write(priv, AR8327_REG_ATU_FUNC, - AR8327_ATU_FUNC_OP_FLUSH | - AR8327_ATU_FUNC_BUSY); - - return ret; -} - -static int -ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port) -{ - u32 t; - int ret; - - ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC, - AR8327_ATU_FUNC_BUSY, 0); - if (!ret) { - t = (port << AR8327_ATU_PORT_NUM_S); - t |= AR8327_ATU_FUNC_OP_FLUSH_PORT; - t |= AR8327_ATU_FUNC_BUSY; - ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t); - } - - return ret; -} - -static int -ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port) -{ - u32 fwd_ctrl, frame_ack; - - fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S); - frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD | - AR8327_FRAME_ACK_CTRL_IGMP_JOIN | - AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << - AR8327_FRAME_ACK_CTRL_S(port)); - - return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) & - fwd_ctrl) == fwd_ctrl && - (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) & - frame_ack) == frame_ack; -} - -static void -ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable) -{ - int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port); - u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD | - AR8327_FRAME_ACK_CTRL_IGMP_JOIN | - AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) << - AR8327_FRAME_ACK_CTRL_S(port); - - if (enable) { - ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, - BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S, - BIT(port) << AR8327_FWD_CTRL1_IGMP_S); - ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack); - } else { - ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1, - BIT(port) << AR8327_FWD_CTRL1_IGMP_S, - BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S); - ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack); - } -} - -static void -ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) -{ - if (ar8216_wait_bit(priv, AR8327_REG_VTU_FUNC1, - AR8327_VTU_FUNC1_BUSY, 0)) - return; - - if ((op & AR8327_VTU_FUNC1_OP) == AR8327_VTU_FUNC1_OP_LOAD) - ar8xxx_write(priv, AR8327_REG_VTU_FUNC0, val); - - op |= AR8327_VTU_FUNC1_BUSY; - ar8xxx_write(priv, AR8327_REG_VTU_FUNC1, op); -} - -static void -ar8327_vtu_flush(struct ar8xxx_priv *priv) -{ - ar8327_vtu_op(priv, AR8327_VTU_FUNC1_OP_FLUSH, 0); -} - -static void -ar8327_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) -{ - u32 op; - u32 val; - int i; - - op = AR8327_VTU_FUNC1_OP_LOAD | (vid << AR8327_VTU_FUNC1_VID_S); - val = AR8327_VTU_FUNC0_VALID | AR8327_VTU_FUNC0_IVL; - for (i = 0; i < AR8327_NUM_PORTS; i++) { - u32 mode; - - if ((port_mask & BIT(i)) == 0) - mode = AR8327_VTU_FUNC0_EG_MODE_NOT; - else if (priv->vlan == 0) - mode = AR8327_VTU_FUNC0_EG_MODE_KEEP; - else if ((priv->vlan_tagged & BIT(i)) || (priv->vlan_id[priv->pvid[i]] != vid)) - mode = AR8327_VTU_FUNC0_EG_MODE_TAG; - else - mode = AR8327_VTU_FUNC0_EG_MODE_UNTAG; - - val |= mode << AR8327_VTU_FUNC0_EG_MODE_S(i); - } - ar8327_vtu_op(priv, op, val); -} - -static void -ar8327_setup_port(struct ar8xxx_priv *priv, int port, u32 members) -{ - u32 t; - u32 egress, ingress; - u32 pvid = priv->vlan_id[priv->pvid[port]]; - - if (priv->vlan) { - egress = AR8327_PORT_VLAN1_OUT_MODE_UNMOD; - ingress = AR8216_IN_SECURE; - } else { - egress = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH; - ingress = AR8216_IN_PORT_ONLY; - } - - t = pvid << AR8327_PORT_VLAN0_DEF_SVID_S; - t |= pvid << AR8327_PORT_VLAN0_DEF_CVID_S; - if (priv->vlan && priv->port_vlan_prio[port]) { - u32 prio = priv->port_vlan_prio[port]; - - t |= prio << AR8327_PORT_VLAN0_DEF_SPRI_S; - t |= prio << AR8327_PORT_VLAN0_DEF_CPRI_S; - } - ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t); - - t = AR8327_PORT_VLAN1_PORT_VLAN_PROP; - t |= egress << AR8327_PORT_VLAN1_OUT_MODE_S; - if (priv->vlan && priv->port_vlan_prio[port]) - t |= AR8327_PORT_VLAN1_VLAN_PRI_PROP; - - ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t); - - t = members; - t |= AR8327_PORT_LOOKUP_LEARN; - t |= ingress << AR8327_PORT_LOOKUP_IN_MODE_S; - t |= AR8216_PORT_STATE_FORWARD << AR8327_PORT_LOOKUP_STATE_S; - ar8xxx_write(priv, AR8327_REG_PORT_LOOKUP(port), t); -} - -static int -ar8327_sw_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - u8 ports = priv->vlan_table[val->port_vlan]; - int i; - - val->len = 0; - for (i = 0; i < dev->ports; i++) { - struct switch_port *p; - - if (!(ports & (1 << i))) - continue; - - p = &val->value.ports[val->len++]; - p->id = i; - if ((priv->vlan_tagged & (1 << i)) || (priv->pvid[i] != val->port_vlan)) - p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); - else - p->flags = 0; - } - return 0; -} - -static int -ar8327_sw_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - u8 *vt = &priv->vlan_table[val->port_vlan]; - int i; - - *vt = 0; - for (i = 0; i < val->len; i++) { - struct switch_port *p = &val->value.ports[i]; - - if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { - if (val->port_vlan == priv->pvid[p->id]) { - priv->vlan_tagged |= (1 << p->id); - } - } else { - priv->vlan_tagged &= ~(1 << p->id); - priv->pvid[p->id] = val->port_vlan; - } - - *vt |= 1 << p->id; - } - return 0; -} - -static void -ar8327_set_mirror_regs(struct ar8xxx_priv *priv) -{ - int port; - - /* reset all mirror registers */ - ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, - AR8327_FWD_CTRL0_MIRROR_PORT, - (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S)); - for (port = 0; port < AR8327_NUM_PORTS; port++) { - ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port), - AR8327_PORT_LOOKUP_ING_MIRROR_EN); - - ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port), - AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); - } - - /* now enable mirroring if necessary */ - if (priv->source_port >= AR8327_NUM_PORTS || - priv->monitor_port >= AR8327_NUM_PORTS || - priv->source_port == priv->monitor_port) { - return; - } - - ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL0, - AR8327_FWD_CTRL0_MIRROR_PORT, - (priv->monitor_port << AR8327_FWD_CTRL0_MIRROR_PORT_S)); - - if (priv->mirror_rx) - ar8xxx_reg_set(priv, AR8327_REG_PORT_LOOKUP(priv->source_port), - AR8327_PORT_LOOKUP_ING_MIRROR_EN); - - if (priv->mirror_tx) - ar8xxx_reg_set(priv, AR8327_REG_PORT_HOL_CTRL1(priv->source_port), - AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN); -} - -static int -ar8327_sw_set_eee(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - struct ar8327_data *data = priv->chip_data; - int port = val->port_vlan; - int phy; - - if (port >= dev->ports) - return -EINVAL; - if (port == 0 || port == 6) - return -EOPNOTSUPP; - - phy = port - 1; - - data->eee[phy] = !!(val->value.i); - - return 0; -} - -static int -ar8327_sw_get_eee(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - const struct ar8327_data *data = priv->chip_data; - int port = val->port_vlan; - int phy; - - if (port >= dev->ports) - return -EINVAL; - if (port == 0 || port == 6) - return -EOPNOTSUPP; - - phy = port - 1; - - val->value.i = data->eee[phy]; - - return 0; -} - -static void -ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) -{ - int timeout = 20; - - while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout) { - udelay(10); - cond_resched(); - } - - if (!timeout) - pr_err("ar8327: timeout waiting for atu to become ready\n"); -} - -static void ar8327_get_arl_entry(struct ar8xxx_priv *priv, - struct arl_entry *a, u32 *status, enum arl_op op) -{ - struct mii_bus *bus = priv->mii_bus; - u16 r2, page; - u16 r1_data0, r1_data1, r1_data2, r1_func; - u32 t, val0, val1, val2; - int i; - - split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page); - r2 |= 0x10; - - r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e; - r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e; - r1_func = (AR8327_REG_ATU_FUNC >> 1) & 0x1e; - - switch (op) { - case AR8XXX_ARL_INITIALIZE: - /* all ATU registers are on the same page - * therefore set page only once - */ - bus->write(bus, 0x18, 0, page); - wait_for_page_switch(); - - ar8327_wait_atu_ready(priv, r2, r1_func); - - ar8xxx_mii_write32(priv, r2, r1_data0, 0); - ar8xxx_mii_write32(priv, r2, r1_data1, 0); - ar8xxx_mii_write32(priv, r2, r1_data2, 0); - break; - case AR8XXX_ARL_GET_NEXT: - ar8xxx_mii_write32(priv, r2, r1_func, - AR8327_ATU_FUNC_OP_GET_NEXT | - AR8327_ATU_FUNC_BUSY); - ar8327_wait_atu_ready(priv, r2, r1_func); - - val0 = ar8xxx_mii_read32(priv, r2, r1_data0); - val1 = ar8xxx_mii_read32(priv, r2, r1_data1); - val2 = ar8xxx_mii_read32(priv, r2, r1_data2); - - *status = val2 & AR8327_ATU_STATUS; - if (!*status) - break; - - i = 0; - t = AR8327_ATU_PORT0; - while (!(val1 & t) && ++i < AR8327_NUM_PORTS) - t <<= 1; - - a->port = i; - a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S; - a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S; - a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S; - a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S; - a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S; - a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S; - break; - } -} - -static int -ar8327_sw_hw_apply(struct switch_dev *dev) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - const struct ar8327_data *data = priv->chip_data; - int ret, i; - - ret = ar8xxx_sw_hw_apply(dev); - if (ret) - return ret; - - for (i=0; i < AR8XXX_NUM_PHYS; i++) { - if (data->eee[i]) - ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL, - AR8327_EEE_CTRL_DISABLE_PHY(i)); - else - ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL, - AR8327_EEE_CTRL_DISABLE_PHY(i)); - } - - return 0; -} - -int -ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port = val->port_vlan; - - if (port >= dev->ports) - return -EINVAL; - - mutex_lock(&priv->reg_mutex); - val->value.i = ar8327_get_port_igmp(priv, port); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port = val->port_vlan; - - if (port >= dev->ports) - return -EINVAL; - - mutex_lock(&priv->reg_mutex); - ar8327_set_port_igmp(priv, port, val->value.i); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8327_sw_get_igmp_snooping(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - int port; - - for (port = 0; port < dev->ports; port++) { - val->port_vlan = port; - if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) || - !val->value.i) - break; - } - - return 0; -} - -int -ar8327_sw_set_igmp_snooping(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - int port; - - for (port = 0; port < dev->ports; port++) { - val->port_vlan = port; - if (ar8327_sw_set_port_igmp_snooping(dev, attr, val)) - break; - } - - return 0; -} - -int -ar8327_sw_get_igmp_v3(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - u32 val_reg; - - mutex_lock(&priv->reg_mutex); - val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1); - val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -int -ar8327_sw_set_igmp_v3(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - - mutex_lock(&priv->reg_mutex); - if (val->value.i) - ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1, - AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); - else - ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1, - AR8327_FRAME_ACK_CTRL_IGMP_V3_EN); - mutex_unlock(&priv->reg_mutex); - - return 0; -} - -static int -ar8327_sw_set_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port = val->port_vlan; - - if (port >= dev->ports) - return -EINVAL; - if (port == 0 || port == 6) - return -EOPNOTSUPP; - if (val->value.i < 0 || val->value.i > 7) - return -EINVAL; - - priv->port_vlan_prio[port] = val->value.i; - - return 0; -} - -static int -ar8327_sw_get_port_vlan_prio(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); - int port = val->port_vlan; - - val->value.i = priv->port_vlan_prio[port]; - - return 0; -} - -static const struct switch_attr ar8327_sw_attr_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = ar8xxx_sw_set_vlan, - .get = ar8xxx_sw_get_vlan, - .max = 1 - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = ar8xxx_sw_set_reset_mibs, - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_rx", - .description = "Enable mirroring of RX packets", - .set = ar8xxx_sw_set_mirror_rx_enable, - .get = ar8xxx_sw_get_mirror_rx_enable, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_tx", - .description = "Enable mirroring of TX packets", - .set = ar8xxx_sw_set_mirror_tx_enable, - .get = ar8xxx_sw_get_mirror_tx_enable, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "mirror_monitor_port", - .description = "Mirror monitor port", - .set = ar8xxx_sw_set_mirror_monitor_port, - .get = ar8xxx_sw_get_mirror_monitor_port, - .max = AR8327_NUM_PORTS - 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "mirror_source_port", - .description = "Mirror source port", - .set = ar8xxx_sw_set_mirror_source_port, - .get = ar8xxx_sw_get_mirror_source_port, - .max = AR8327_NUM_PORTS - 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "arl_age_time", - .description = "ARL age time (secs)", - .set = ar8xxx_sw_set_arl_age_time, - .get = ar8xxx_sw_get_arl_age_time, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "arl_table", - .description = "Get ARL table", - .set = NULL, - .get = ar8xxx_sw_get_arl_table, - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "flush_arl_table", - .description = "Flush ARL table", - .set = ar8xxx_sw_set_flush_arl_table, - }, - { - .type = SWITCH_TYPE_INT, - .name = "igmp_snooping", - .description = "Enable IGMP Snooping", - .set = ar8327_sw_set_igmp_snooping, - .get = ar8327_sw_get_igmp_snooping, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "igmp_v3", - .description = "Enable IGMPv3 support", - .set = ar8327_sw_set_igmp_v3, - .get = ar8327_sw_get_igmp_v3, - .max = 1 - }, -}; - -static const struct switch_attr ar8327_sw_attr_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = ar8xxx_sw_set_port_reset_mib, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get port's MIB counters", - .set = NULL, - .get = ar8xxx_sw_get_port_mib, - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_eee", - .description = "Enable EEE PHY sleep mode", - .set = ar8327_sw_set_eee, - .get = ar8327_sw_get_eee, - .max = 1, - }, - { - .type = SWITCH_TYPE_NOVAL, - .name = "flush_arl_table", - .description = "Flush port's ARL table entries", - .set = ar8xxx_sw_set_flush_port_arl_table, - }, - { - .type = SWITCH_TYPE_INT, - .name = "igmp_snooping", - .description = "Enable port's IGMP Snooping", - .set = ar8327_sw_set_port_igmp_snooping, - .get = ar8327_sw_get_port_igmp_snooping, - .max = 1 - }, - { - .type = SWITCH_TYPE_INT, - .name = "vlan_prio", - .description = "Port VLAN default priority (VLAN PCP) (0-7)", - .set = ar8327_sw_set_port_vlan_prio, - .get = ar8327_sw_get_port_vlan_prio, - .max = 7, - }, -}; - -static const struct switch_dev_ops ar8327_sw_ops = { - .attr_global = { - .attr = ar8327_sw_attr_globals, - .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals), - }, - .attr_port = { - .attr = ar8327_sw_attr_port, - .n_attr = ARRAY_SIZE(ar8327_sw_attr_port), - }, - .attr_vlan = { - .attr = ar8xxx_sw_attr_vlan, - .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), - }, - .get_port_pvid = ar8xxx_sw_get_pvid, - .set_port_pvid = ar8xxx_sw_set_pvid, - .get_vlan_ports = ar8327_sw_get_ports, - .set_vlan_ports = ar8327_sw_set_ports, - .apply_config = ar8327_sw_hw_apply, - .reset_switch = ar8xxx_sw_reset_switch, - .get_port_link = ar8xxx_sw_get_port_link, -/* The following op is disabled as it hogs the CPU and degrades performance. - An implementation has been attempted in 4d8a66d but reading MIB data is slow - on ar8xxx switches. - - The high CPU load has been traced down to the ar8xxx_reg_wait() call in - ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by - the request to update the MIB counter is cleared. */ -#if 0 - .get_port_stats = ar8xxx_sw_get_port_stats, -#endif -}; - -const struct ar8xxx_chip ar8327_chip = { - .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, - .config_at_probe = true, - .mii_lo_first = true, - - .name = "Atheros AR8327", - .ports = AR8327_NUM_PORTS, - .vlans = AR8X16_MAX_VLANS, - .swops = &ar8327_sw_ops, - - .reg_port_stats_start = 0x1000, - .reg_port_stats_length = 0x100, - .reg_arl_ctrl = AR8327_REG_ARL_CTRL, - - .hw_init = ar8327_hw_init, - .cleanup = ar8327_cleanup, - .init_globals = ar8327_init_globals, - .init_port = ar8327_init_port, - .setup_port = ar8327_setup_port, - .read_port_status = ar8327_read_port_status, - .read_port_eee_status = ar8327_read_port_eee_status, - .atu_flush = ar8327_atu_flush, - .atu_flush_port = ar8327_atu_flush_port, - .vtu_flush = ar8327_vtu_flush, - .vtu_load_vlan = ar8327_vtu_load_vlan, - .phy_fixup = ar8327_phy_fixup, - .set_mirror_regs = ar8327_set_mirror_regs, - .get_arl_entry = ar8327_get_arl_entry, - .sw_hw_apply = ar8327_sw_hw_apply, - - .num_mibs = ARRAY_SIZE(ar8236_mibs), - .mib_decs = ar8236_mibs, - .mib_func = AR8327_REG_MIB_FUNC -}; - -const struct ar8xxx_chip ar8337_chip = { - .caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, - .config_at_probe = true, - .mii_lo_first = true, - - .name = "Atheros AR8337", - .ports = AR8327_NUM_PORTS, - .vlans = AR8X16_MAX_VLANS, - .swops = &ar8327_sw_ops, - - .reg_port_stats_start = 0x1000, - .reg_port_stats_length = 0x100, - .reg_arl_ctrl = AR8327_REG_ARL_CTRL, - - .hw_init = ar8327_hw_init, - .cleanup = ar8327_cleanup, - .init_globals = ar8327_init_globals, - .init_port = ar8327_init_port, - .setup_port = ar8327_setup_port, - .read_port_status = ar8327_read_port_status, - .read_port_eee_status = ar8327_read_port_eee_status, - .atu_flush = ar8327_atu_flush, - .atu_flush_port = ar8327_atu_flush_port, - .vtu_flush = ar8327_vtu_flush, - .vtu_load_vlan = ar8327_vtu_load_vlan, - .phy_fixup = ar8327_phy_fixup, - .set_mirror_regs = ar8327_set_mirror_regs, - .get_arl_entry = ar8327_get_arl_entry, - .sw_hw_apply = ar8327_sw_hw_apply, - - .num_mibs = ARRAY_SIZE(ar8236_mibs), - .mib_decs = ar8236_mibs, - .mib_func = AR8327_REG_MIB_FUNC -}; diff --git a/target/linux/generic/files/drivers/net/phy/ar8327.h b/target/linux/generic/files/drivers/net/phy/ar8327.h deleted file mode 100644 index d53ef885b1..0000000000 --- a/target/linux/generic/files/drivers/net/phy/ar8327.h +++ /dev/null @@ -1,325 +0,0 @@ -/* - * ar8327.h: AR8216 switch driver - * - * Copyright (C) 2009 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef __AR8327_H -#define __AR8327_H - -#define AR8327_NUM_PORTS 7 -#define AR8327_NUM_LEDS 15 -#define AR8327_PORTS_ALL 0x7f -#define AR8327_NUM_LED_CTRL_REGS 4 - -#define AR8327_REG_MASK 0x000 - -#define AR8327_REG_PAD0_MODE 0x004 -#define AR8327_REG_PAD5_MODE 0x008 -#define AR8327_REG_PAD6_MODE 0x00c -#define AR8327_PAD_MAC_MII_RXCLK_SEL BIT(0) -#define AR8327_PAD_MAC_MII_TXCLK_SEL BIT(1) -#define AR8327_PAD_MAC_MII_EN BIT(2) -#define AR8327_PAD_MAC_GMII_RXCLK_SEL BIT(4) -#define AR8327_PAD_MAC_GMII_TXCLK_SEL BIT(5) -#define AR8327_PAD_MAC_GMII_EN BIT(6) -#define AR8327_PAD_SGMII_EN BIT(7) -#define AR8327_PAD_PHY_MII_RXCLK_SEL BIT(8) -#define AR8327_PAD_PHY_MII_TXCLK_SEL BIT(9) -#define AR8327_PAD_PHY_MII_EN BIT(10) -#define AR8327_PAD_PHY_GMII_PIPE_RXCLK_SEL BIT(11) -#define AR8327_PAD_PHY_GMII_RXCLK_SEL BIT(12) -#define AR8327_PAD_PHY_GMII_TXCLK_SEL BIT(13) -#define AR8327_PAD_PHY_GMII_EN BIT(14) -#define AR8327_PAD_PHYX_GMII_EN BIT(16) -#define AR8327_PAD_PHYX_RGMII_EN BIT(17) -#define AR8327_PAD_PHYX_MII_EN BIT(18) -#define AR8327_PAD_SGMII_DELAY_EN BIT(19) -#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL BITS(20, 2) -#define AR8327_PAD_RGMII_RXCLK_DELAY_SEL_S 20 -#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL BITS(22, 2) -#define AR8327_PAD_RGMII_TXCLK_DELAY_SEL_S 22 -#define AR8327_PAD_RGMII_RXCLK_DELAY_EN BIT(24) -#define AR8327_PAD_RGMII_TXCLK_DELAY_EN BIT(25) -#define AR8327_PAD_RGMII_EN BIT(26) - -#define AR8327_REG_POWER_ON_STRIP 0x010 -#define AR8327_POWER_ON_STRIP_POWER_ON_SEL BIT(31) -#define AR8327_POWER_ON_STRIP_LED_OPEN_EN BIT(24) -#define AR8327_POWER_ON_STRIP_SERDES_AEN BIT(7) - -#define AR8327_REG_INT_STATUS0 0x020 -#define AR8327_INT0_VT_DONE BIT(20) - -#define AR8327_REG_INT_STATUS1 0x024 -#define AR8327_REG_INT_MASK0 0x028 -#define AR8327_REG_INT_MASK1 0x02c - -#define AR8327_REG_MODULE_EN 0x030 -#define AR8327_MODULE_EN_MIB BIT(0) - -#define AR8327_REG_MIB_FUNC 0x034 -#define AR8327_MIB_CPU_KEEP BIT(20) - -#define AR8327_REG_SERVICE_TAG 0x048 -#define AR8327_REG_LED_CTRL(_i) (0x050 + (_i) * 4) -#define AR8327_REG_LED_CTRL0 0x050 -#define AR8327_REG_LED_CTRL1 0x054 -#define AR8327_REG_LED_CTRL2 0x058 -#define AR8327_REG_LED_CTRL3 0x05c -#define AR8327_REG_MAC_ADDR0 0x060 -#define AR8327_REG_MAC_ADDR1 0x064 - -#define AR8327_REG_MAX_FRAME_SIZE 0x078 -#define AR8327_MAX_FRAME_SIZE_MTU BITS(0, 14) - -#define AR8327_REG_PORT_STATUS(_i) (0x07c + (_i) * 4) -#define AR8327_PORT_STATUS_TXFLOW_AUTO BIT(10) -#define AR8327_PORT_STATUS_RXFLOW_AUTO BIT(11) - -#define AR8327_REG_HEADER_CTRL 0x098 -#define AR8327_REG_PORT_HEADER(_i) (0x09c + (_i) * 4) - -#define AR8327_REG_SGMII_CTRL 0x0e0 -#define AR8327_SGMII_CTRL_EN_PLL BIT(1) -#define AR8327_SGMII_CTRL_EN_RX BIT(2) -#define AR8327_SGMII_CTRL_EN_TX BIT(3) - -#define AR8327_REG_EEE_CTRL 0x100 -#define AR8327_EEE_CTRL_DISABLE_PHY(_i) BIT(4 + (_i) * 2) - -#define AR8327_REG_FRAME_ACK_CTRL0 0x210 -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN0 BIT(0) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN0 BIT(1) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN0 BIT(2) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN0 BIT(3) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN0 BIT(4) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN0 BIT(5) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN0 BIT(6) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN1 BIT(8) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN1 BIT(9) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN1 BIT(10) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN1 BIT(11) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN1 BIT(12) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN1 BIT(13) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN1 BIT(14) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN2 BIT(16) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN2 BIT(17) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN2 BIT(18) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN2 BIT(19) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN2 BIT(20) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN2 BIT(21) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN2 BIT(22) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN3 BIT(24) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN3 BIT(25) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN3 BIT(26) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN3 BIT(27) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN3 BIT(28) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN3 BIT(29) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN3 BIT(30) - -#define AR8327_REG_FRAME_ACK_CTRL1 0x214 -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN4 BIT(0) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN4 BIT(1) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN4 BIT(2) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN4 BIT(3) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN4 BIT(4) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN4 BIT(5) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN4 BIT(6) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN5 BIT(8) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN5 BIT(9) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN5 BIT(10) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN5 BIT(11) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN5 BIT(12) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN5 BIT(13) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN5 BIT(14) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD_EN6 BIT(16) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN_EN6 BIT(17) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE_EN6 BIT(18) -#define AR8327_FRAME_ACK_CTRL_EAPOL_EN6 BIT(19) -#define AR8327_FRAME_ACK_CTRL_DHCP_EN6 BIT(20) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK_EN6 BIT(21) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ_EN6 BIT(22) -#define AR8327_FRAME_ACK_CTRL_IGMP_V3_EN BIT(24) -#define AR8327_FRAME_ACK_CTRL_PPPOE_EN BIT(25) - -#define AR8327_REG_FRAME_ACK_CTRL(_i) (0x210 + ((_i) / 4) * 0x4) -#define AR8327_FRAME_ACK_CTRL_IGMP_MLD BIT(0) -#define AR8327_FRAME_ACK_CTRL_IGMP_JOIN BIT(1) -#define AR8327_FRAME_ACK_CTRL_IGMP_LEAVE BIT(2) -#define AR8327_FRAME_ACK_CTRL_EAPOL BIT(3) -#define AR8327_FRAME_ACK_CTRL_DHCP BIT(4) -#define AR8327_FRAME_ACK_CTRL_ARP_ACK BIT(5) -#define AR8327_FRAME_ACK_CTRL_ARP_REQ BIT(6) -#define AR8327_FRAME_ACK_CTRL_S(_i) (((_i) % 4) * 8) - -#define AR8327_REG_PORT_VLAN0(_i) (0x420 + (_i) * 0x8) -#define AR8327_PORT_VLAN0_DEF_PRI_MASK BITS(0, 3) -#define AR8327_PORT_VLAN0_DEF_SVID BITS(0, 12) -#define AR8327_PORT_VLAN0_DEF_SVID_S 0 -#define AR8327_PORT_VLAN0_DEF_SPRI BITS(13, 3) -#define AR8327_PORT_VLAN0_DEF_SPRI_S 13 -#define AR8327_PORT_VLAN0_DEF_CVID BITS(16, 12) -#define AR8327_PORT_VLAN0_DEF_CVID_S 16 -#define AR8327_PORT_VLAN0_DEF_CPRI BITS(29, 3) -#define AR8327_PORT_VLAN0_DEF_CPRI_S 29 - -#define AR8327_REG_PORT_VLAN1(_i) (0x424 + (_i) * 0x8) -#define AR8327_PORT_VLAN1_VLAN_PRI_PROP BIT(4) -#define AR8327_PORT_VLAN1_PORT_VLAN_PROP BIT(6) -#define AR8327_PORT_VLAN1_OUT_MODE BITS(12, 2) -#define AR8327_PORT_VLAN1_OUT_MODE_S 12 -#define AR8327_PORT_VLAN1_OUT_MODE_UNMOD 0 -#define AR8327_PORT_VLAN1_OUT_MODE_UNTAG 1 -#define AR8327_PORT_VLAN1_OUT_MODE_TAG 2 -#define AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH 3 - -#define AR8327_REG_ATU_DATA0 0x600 -#define AR8327_ATU_ADDR0 BITS(0, 8) -#define AR8327_ATU_ADDR0_S 0 -#define AR8327_ATU_ADDR1 BITS(8, 8) -#define AR8327_ATU_ADDR1_S 8 -#define AR8327_ATU_ADDR2 BITS(16, 8) -#define AR8327_ATU_ADDR2_S 16 -#define AR8327_ATU_ADDR3 BITS(24, 8) -#define AR8327_ATU_ADDR3_S 24 -#define AR8327_REG_ATU_DATA1 0x604 -#define AR8327_ATU_ADDR4 BITS(0, 8) -#define AR8327_ATU_ADDR4_S 0 -#define AR8327_ATU_ADDR5 BITS(8, 8) -#define AR8327_ATU_ADDR5_S 8 -#define AR8327_ATU_PORTS BITS(16, 7) -#define AR8327_ATU_PORT0 BIT(16) -#define AR8327_ATU_PORT1 BIT(17) -#define AR8327_ATU_PORT2 BIT(18) -#define AR8327_ATU_PORT3 BIT(19) -#define AR8327_ATU_PORT4 BIT(20) -#define AR8327_ATU_PORT5 BIT(21) -#define AR8327_ATU_PORT6 BIT(22) -#define AR8327_REG_ATU_DATA2 0x608 -#define AR8327_ATU_STATUS BITS(0, 4) - -#define AR8327_REG_ATU_FUNC 0x60c -#define AR8327_ATU_FUNC_OP BITS(0, 4) -#define AR8327_ATU_FUNC_OP_NOOP 0x0 -#define AR8327_ATU_FUNC_OP_FLUSH 0x1 -#define AR8327_ATU_FUNC_OP_LOAD 0x2 -#define AR8327_ATU_FUNC_OP_PURGE 0x3 -#define AR8327_ATU_FUNC_OP_FLUSH_UNLOCKED 0x4 -#define AR8327_ATU_FUNC_OP_FLUSH_PORT 0x5 -#define AR8327_ATU_FUNC_OP_GET_NEXT 0x6 -#define AR8327_ATU_FUNC_OP_SEARCH_MAC 0x7 -#define AR8327_ATU_FUNC_OP_CHANGE_TRUNK 0x8 -#define AR8327_ATU_PORT_NUM BITS(8, 4) -#define AR8327_ATU_PORT_NUM_S 8 -#define AR8327_ATU_FUNC_BUSY BIT(31) - -#define AR8327_REG_VTU_FUNC0 0x0610 -#define AR8327_VTU_FUNC0_EG_MODE BITS(4, 14) -#define AR8327_VTU_FUNC0_EG_MODE_S(_i) (4 + (_i) * 2) -#define AR8327_VTU_FUNC0_EG_MODE_KEEP 0 -#define AR8327_VTU_FUNC0_EG_MODE_UNTAG 1 -#define AR8327_VTU_FUNC0_EG_MODE_TAG 2 -#define AR8327_VTU_FUNC0_EG_MODE_NOT 3 -#define AR8327_VTU_FUNC0_IVL BIT(19) -#define AR8327_VTU_FUNC0_VALID BIT(20) - -#define AR8327_REG_VTU_FUNC1 0x0614 -#define AR8327_VTU_FUNC1_OP BITS(0, 3) -#define AR8327_VTU_FUNC1_OP_NOOP 0 -#define AR8327_VTU_FUNC1_OP_FLUSH 1 -#define AR8327_VTU_FUNC1_OP_LOAD 2 -#define AR8327_VTU_FUNC1_OP_PURGE 3 -#define AR8327_VTU_FUNC1_OP_REMOVE_PORT 4 -#define AR8327_VTU_FUNC1_OP_GET_NEXT 5 -#define AR8327_VTU_FUNC1_OP_GET_ONE 6 -#define AR8327_VTU_FUNC1_FULL BIT(4) -#define AR8327_VTU_FUNC1_PORT BIT(8, 4) -#define AR8327_VTU_FUNC1_PORT_S 8 -#define AR8327_VTU_FUNC1_VID BIT(16, 12) -#define AR8327_VTU_FUNC1_VID_S 16 -#define AR8327_VTU_FUNC1_BUSY BIT(31) - -#define AR8327_REG_ARL_CTRL 0x0618 - -#define AR8327_REG_FWD_CTRL0 0x620 -#define AR8327_FWD_CTRL0_CPU_PORT_EN BIT(10) -#define AR8327_FWD_CTRL0_MIRROR_PORT BITS(4, 4) -#define AR8327_FWD_CTRL0_MIRROR_PORT_S 4 - -#define AR8327_REG_FWD_CTRL1 0x624 -#define AR8327_FWD_CTRL1_UC_FLOOD BITS(0, 7) -#define AR8327_FWD_CTRL1_UC_FLOOD_S 0 -#define AR8327_FWD_CTRL1_MC_FLOOD BITS(8, 7) -#define AR8327_FWD_CTRL1_MC_FLOOD_S 8 -#define AR8327_FWD_CTRL1_BC_FLOOD BITS(16, 7) -#define AR8327_FWD_CTRL1_BC_FLOOD_S 16 -#define AR8327_FWD_CTRL1_IGMP BITS(24, 7) -#define AR8327_FWD_CTRL1_IGMP_S 24 - -#define AR8327_REG_PORT_LOOKUP(_i) (0x660 + (_i) * 0xc) -#define AR8327_PORT_LOOKUP_MEMBER BITS(0, 7) -#define AR8327_PORT_LOOKUP_IN_MODE BITS(8, 2) -#define AR8327_PORT_LOOKUP_IN_MODE_S 8 -#define AR8327_PORT_LOOKUP_STATE BITS(16, 3) -#define AR8327_PORT_LOOKUP_STATE_S 16 -#define AR8327_PORT_LOOKUP_LEARN BIT(20) -#define AR8327_PORT_LOOKUP_ING_MIRROR_EN BIT(25) - -#define AR8327_REG_PORT_PRIO(_i) (0x664 + (_i) * 0xc) - -#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8) -#define AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16) - -#define AR8337_PAD_MAC06_EXCHANGE_EN BIT(31) - -enum ar8327_led_pattern { - AR8327_LED_PATTERN_OFF = 0, - AR8327_LED_PATTERN_BLINK, - AR8327_LED_PATTERN_ON, - AR8327_LED_PATTERN_RULE, -}; - -struct ar8327_led_entry { - unsigned reg; - unsigned shift; -}; - -struct ar8327_led { - struct led_classdev cdev; - struct ar8xxx_priv *sw_priv; - - char *name; - bool active_low; - u8 led_num; - enum ar8327_led_mode mode; - - struct mutex mutex; - spinlock_t lock; - struct work_struct led_work; - bool enable_hw_mode; - enum ar8327_led_pattern pattern; -}; - -struct ar8327_data { - u32 port0_status; - u32 port6_status; - - struct ar8327_led **leds; - unsigned int num_leds; - - /* all fields below are cleared on reset */ - bool eee[AR8XXX_NUM_PHYS]; -}; - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/b53/Kconfig b/target/linux/generic/files/drivers/net/phy/b53/Kconfig deleted file mode 100644 index 08287e7adf..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/Kconfig +++ /dev/null @@ -1,37 +0,0 @@ -menuconfig SWCONFIG_B53 - tristate "Broadcom bcm53xx managed switch support" - depends on SWCONFIG - help - This driver adds support for Broadcom managed switch chips. It supports - BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX - integrated switches. - -config SWCONFIG_B53_SPI_DRIVER - tristate "B53 SPI connected switch driver" - depends on SWCONFIG_B53 && SPI - help - Select to enable support for registering switches configured through SPI. - -config SWCONFIG_B53_PHY_DRIVER - tristate "B53 MDIO connected switch driver" - depends on SWCONFIG_B53 - select SWCONFIG_B53_PHY_FIXUP - help - Select to enable support for registering switches configured through MDIO. - -config SWCONFIG_B53_MMAP_DRIVER - tristate "B53 MMAP connected switch driver" - depends on SWCONFIG_B53 - help - Select to enable support for memory-mapped switches like the BCM63XX - integrated switches. - -config SWCONFIG_B53_SRAB_DRIVER - tristate "B53 SRAB connected switch driver" - depends on SWCONFIG_B53 - help - Select to enable support for memory-mapped Switch Register Access - Bridge Registers (SRAB) like it is found on the BCM53010 - -config SWCONFIG_B53_PHY_FIXUP - bool diff --git a/target/linux/generic/files/drivers/net/phy/b53/Makefile b/target/linux/generic/files/drivers/net/phy/b53/Makefile deleted file mode 100644 index 13ff366448..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -obj-$(CONFIG_SWCONFIG_B53) += b53_common.o - -obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o - -obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o -obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o -obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o -obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o - -ccflags-y += -Werror diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_common.c b/target/linux/generic/files/drivers/net/phy/b53/b53_common.c deleted file mode 100644 index 670588c84e..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_common.c +++ /dev/null @@ -1,1722 +0,0 @@ -/* - * B53 switch driver main logic - * - * Copyright (C) 2011-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "b53_regs.h" -#include "b53_priv.h" - -/* buffer size needed for displaying all MIBs with max'd values */ -#define B53_BUF_SIZE 1188 - -struct b53_mib_desc { - u8 size; - u8 offset; - const char *name; -}; - -/* BCM5365 MIB counters */ -static const struct b53_mib_desc b53_mibs_65[] = { - { 8, 0x00, "TxOctets" }, - { 4, 0x08, "TxDropPkts" }, - { 4, 0x10, "TxBroadcastPkts" }, - { 4, 0x14, "TxMulticastPkts" }, - { 4, 0x18, "TxUnicastPkts" }, - { 4, 0x1c, "TxCollisions" }, - { 4, 0x20, "TxSingleCollision" }, - { 4, 0x24, "TxMultipleCollision" }, - { 4, 0x28, "TxDeferredTransmit" }, - { 4, 0x2c, "TxLateCollision" }, - { 4, 0x30, "TxExcessiveCollision" }, - { 4, 0x38, "TxPausePkts" }, - { 8, 0x44, "RxOctets" }, - { 4, 0x4c, "RxUndersizePkts" }, - { 4, 0x50, "RxPausePkts" }, - { 4, 0x54, "Pkts64Octets" }, - { 4, 0x58, "Pkts65to127Octets" }, - { 4, 0x5c, "Pkts128to255Octets" }, - { 4, 0x60, "Pkts256to511Octets" }, - { 4, 0x64, "Pkts512to1023Octets" }, - { 4, 0x68, "Pkts1024to1522Octets" }, - { 4, 0x6c, "RxOversizePkts" }, - { 4, 0x70, "RxJabbers" }, - { 4, 0x74, "RxAlignmentErrors" }, - { 4, 0x78, "RxFCSErrors" }, - { 8, 0x7c, "RxGoodOctets" }, - { 4, 0x84, "RxDropPkts" }, - { 4, 0x88, "RxUnicastPkts" }, - { 4, 0x8c, "RxMulticastPkts" }, - { 4, 0x90, "RxBroadcastPkts" }, - { 4, 0x94, "RxSAChanges" }, - { 4, 0x98, "RxFragments" }, - { }, -}; - -#define B63XX_MIB_TXB_ID 0 /* TxOctets */ -#define B63XX_MIB_RXB_ID 14 /* RxOctets */ - -/* BCM63xx MIB counters */ -static const struct b53_mib_desc b53_mibs_63xx[] = { - { 8, 0x00, "TxOctets" }, - { 4, 0x08, "TxDropPkts" }, - { 4, 0x0c, "TxQoSPkts" }, - { 4, 0x10, "TxBroadcastPkts" }, - { 4, 0x14, "TxMulticastPkts" }, - { 4, 0x18, "TxUnicastPkts" }, - { 4, 0x1c, "TxCollisions" }, - { 4, 0x20, "TxSingleCollision" }, - { 4, 0x24, "TxMultipleCollision" }, - { 4, 0x28, "TxDeferredTransmit" }, - { 4, 0x2c, "TxLateCollision" }, - { 4, 0x30, "TxExcessiveCollision" }, - { 4, 0x38, "TxPausePkts" }, - { 8, 0x3c, "TxQoSOctets" }, - { 8, 0x44, "RxOctets" }, - { 4, 0x4c, "RxUndersizePkts" }, - { 4, 0x50, "RxPausePkts" }, - { 4, 0x54, "Pkts64Octets" }, - { 4, 0x58, "Pkts65to127Octets" }, - { 4, 0x5c, "Pkts128to255Octets" }, - { 4, 0x60, "Pkts256to511Octets" }, - { 4, 0x64, "Pkts512to1023Octets" }, - { 4, 0x68, "Pkts1024to1522Octets" }, - { 4, 0x6c, "RxOversizePkts" }, - { 4, 0x70, "RxJabbers" }, - { 4, 0x74, "RxAlignmentErrors" }, - { 4, 0x78, "RxFCSErrors" }, - { 8, 0x7c, "RxGoodOctets" }, - { 4, 0x84, "RxDropPkts" }, - { 4, 0x88, "RxUnicastPkts" }, - { 4, 0x8c, "RxMulticastPkts" }, - { 4, 0x90, "RxBroadcastPkts" }, - { 4, 0x94, "RxSAChanges" }, - { 4, 0x98, "RxFragments" }, - { 4, 0xa0, "RxSymbolErrors" }, - { 4, 0xa4, "RxQoSPkts" }, - { 8, 0xa8, "RxQoSOctets" }, - { 4, 0xb0, "Pkts1523to2047Octets" }, - { 4, 0xb4, "Pkts2048to4095Octets" }, - { 4, 0xb8, "Pkts4096to8191Octets" }, - { 4, 0xbc, "Pkts8192to9728Octets" }, - { 4, 0xc0, "RxDiscarded" }, - { } -}; - -#define B53XX_MIB_TXB_ID 0 /* TxOctets */ -#define B53XX_MIB_RXB_ID 12 /* RxOctets */ - -/* MIB counters */ -static const struct b53_mib_desc b53_mibs[] = { - { 8, 0x00, "TxOctets" }, - { 4, 0x08, "TxDropPkts" }, - { 4, 0x10, "TxBroadcastPkts" }, - { 4, 0x14, "TxMulticastPkts" }, - { 4, 0x18, "TxUnicastPkts" }, - { 4, 0x1c, "TxCollisions" }, - { 4, 0x20, "TxSingleCollision" }, - { 4, 0x24, "TxMultipleCollision" }, - { 4, 0x28, "TxDeferredTransmit" }, - { 4, 0x2c, "TxLateCollision" }, - { 4, 0x30, "TxExcessiveCollision" }, - { 4, 0x38, "TxPausePkts" }, - { 8, 0x50, "RxOctets" }, - { 4, 0x58, "RxUndersizePkts" }, - { 4, 0x5c, "RxPausePkts" }, - { 4, 0x60, "Pkts64Octets" }, - { 4, 0x64, "Pkts65to127Octets" }, - { 4, 0x68, "Pkts128to255Octets" }, - { 4, 0x6c, "Pkts256to511Octets" }, - { 4, 0x70, "Pkts512to1023Octets" }, - { 4, 0x74, "Pkts1024to1522Octets" }, - { 4, 0x78, "RxOversizePkts" }, - { 4, 0x7c, "RxJabbers" }, - { 4, 0x80, "RxAlignmentErrors" }, - { 4, 0x84, "RxFCSErrors" }, - { 8, 0x88, "RxGoodOctets" }, - { 4, 0x90, "RxDropPkts" }, - { 4, 0x94, "RxUnicastPkts" }, - { 4, 0x98, "RxMulticastPkts" }, - { 4, 0x9c, "RxBroadcastPkts" }, - { 4, 0xa0, "RxSAChanges" }, - { 4, 0xa4, "RxFragments" }, - { 4, 0xa8, "RxJumboPkts" }, - { 4, 0xac, "RxSymbolErrors" }, - { 4, 0xc0, "RxDiscarded" }, - { } -}; - -static int b53_do_vlan_op(struct b53_device *dev, u8 op) -{ - unsigned int i; - - b53_write8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], VTA_START_CMD | op); - - for (i = 0; i < 10; i++) { - u8 vta; - - b53_read8(dev, B53_ARLIO_PAGE, dev->vta_regs[0], &vta); - if (!(vta & VTA_START_CMD)) - return 0; - - usleep_range(100, 200); - } - - return -EIO; -} - -static void b53_set_vlan_entry(struct b53_device *dev, u16 vid, u16 members, - u16 untag) -{ - if (is5325(dev)) { - u32 entry = 0; - - if (members) { - entry = ((untag & VA_UNTAG_MASK_25) << VA_UNTAG_S_25) | - members; - if (dev->core_rev >= 3) - entry |= VA_VALID_25_R4 | vid << VA_VID_HIGH_S; - else - entry |= VA_VALID_25; - } - - b53_write32(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_25, entry); - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, vid | - VTA_RW_STATE_WR | VTA_RW_OP_EN); - } else if (is5365(dev)) { - u16 entry = 0; - - if (members) - entry = ((untag & VA_UNTAG_MASK_65) << VA_UNTAG_S_65) | - members | VA_VALID_65; - - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_WRITE_65, entry); - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_65, vid | - VTA_RW_STATE_WR | VTA_RW_OP_EN); - } else { - b53_write16(dev, B53_ARLIO_PAGE, dev->vta_regs[1], vid); - b53_write32(dev, B53_ARLIO_PAGE, dev->vta_regs[2], - (untag << VTE_UNTAG_S) | members); - - b53_do_vlan_op(dev, VTA_CMD_WRITE); - } -} - -void b53_set_forwarding(struct b53_device *dev, int enable) -{ - u8 mgmt; - - b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); - - if (enable) - mgmt |= SM_SW_FWD_EN; - else - mgmt &= ~SM_SW_FWD_EN; - - b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); -} - -static void b53_enable_vlan(struct b53_device *dev, int enable) -{ - u8 mgmt, vc0, vc1, vc4 = 0, vc5; - - b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, &vc0); - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, &vc1); - - if (is5325(dev) || is5365(dev)) { - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, &vc5); - } else if (is63xx(dev)) { - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, &vc4); - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, &vc5); - } else { - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, &vc4); - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, &vc5); - } - - mgmt &= ~SM_SW_FWD_MODE; - - if (enable) { - vc0 |= VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID; - vc1 |= VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN; - vc4 &= ~VC4_ING_VID_CHECK_MASK; - vc4 |= VC4_ING_VID_VIO_DROP << VC4_ING_VID_CHECK_S; - vc5 |= VC5_DROP_VTABLE_MISS; - - if (is5325(dev)) - vc0 &= ~VC0_RESERVED_1; - - if (is5325(dev) || is5365(dev)) - vc1 |= VC1_RX_MCST_TAG_EN; - - if (!is5325(dev) && !is5365(dev)) { - if (dev->allow_vid_4095) - vc5 |= VC5_VID_FFF_EN; - else - vc5 &= ~VC5_VID_FFF_EN; - } - } else { - vc0 &= ~(VC0_VLAN_EN | VC0_VID_CHK_EN | VC0_VID_HASH_VID); - vc1 &= ~(VC1_RX_MCST_UNTAG_EN | VC1_RX_MCST_FWD_EN); - vc4 &= ~VC4_ING_VID_CHECK_MASK; - vc5 &= ~VC5_DROP_VTABLE_MISS; - - if (is5325(dev) || is5365(dev)) - vc4 |= VC4_ING_VID_VIO_FWD << VC4_ING_VID_CHECK_S; - else - vc4 |= VC4_ING_VID_VIO_TO_IMP << VC4_ING_VID_CHECK_S; - - if (is5325(dev) || is5365(dev)) - vc1 &= ~VC1_RX_MCST_TAG_EN; - - if (!is5325(dev) && !is5365(dev)) - vc5 &= ~VC5_VID_FFF_EN; - } - - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL0, vc0); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL1, vc1); - - if (is5325(dev) || is5365(dev)) { - /* enable the high 8 bit vid check on 5325 */ - if (is5325(dev) && enable) - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, - VC3_HIGH_8BIT_EN); - else - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); - - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, vc4); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_25, vc5); - } else if (is63xx(dev)) { - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3_63XX, 0); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_63XX, vc4); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5_63XX, vc5); - } else { - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_CTRL3, 0); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4, vc4); - b53_write8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL5, vc5); - } - - b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); -} - -static int b53_set_jumbo(struct b53_device *dev, int enable, int allow_10_100) -{ - u32 port_mask = 0; - u16 max_size = JMS_MIN_SIZE; - - if (is5325(dev) || is5365(dev)) - return -EINVAL; - - if (enable) { - port_mask = dev->enabled_ports; - max_size = JMS_MAX_SIZE; - if (allow_10_100) - port_mask |= JPM_10_100_JUMBO_EN; - } - - b53_write32(dev, B53_JUMBO_PAGE, dev->jumbo_pm_reg, port_mask); - return b53_write16(dev, B53_JUMBO_PAGE, dev->jumbo_size_reg, max_size); -} - -static int b53_flush_arl(struct b53_device *dev) -{ - unsigned int i; - - b53_write8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, - FAST_AGE_DONE | FAST_AGE_DYNAMIC | FAST_AGE_STATIC); - - for (i = 0; i < 10; i++) { - u8 fast_age_ctrl; - - b53_read8(dev, B53_CTRL_PAGE, B53_FAST_AGE_CTRL, - &fast_age_ctrl); - - if (!(fast_age_ctrl & FAST_AGE_DONE)) - return 0; - - mdelay(1); - } - - pr_warn("time out while flushing ARL\n"); - - return -EINVAL; -} - -static void b53_enable_ports(struct b53_device *dev) -{ - unsigned i; - - b53_for_each_port(dev, i) { - u8 port_ctrl; - u16 pvlan_mask; - - /* - * prevent leaking packets between wan and lan in unmanaged - * mode through port vlans. - */ - if (dev->enable_vlan || is_cpu_port(dev, i)) - pvlan_mask = 0x1ff; - else if (is531x5(dev) || is5301x(dev)) - /* BCM53115 may use a different port as cpu port */ - pvlan_mask = BIT(dev->sw_dev.cpu_port); - else - pvlan_mask = BIT(B53_CPU_PORT); - - /* BCM5325 CPU port is at 8 */ - if ((is5325(dev) || is5365(dev)) && i == B53_CPU_PORT_25) - i = B53_CPU_PORT; - - if (dev->chip_id == BCM5398_DEVICE_ID && (i == 6 || i == 7)) - /* disable unused ports 6 & 7 */ - port_ctrl = PORT_CTRL_RX_DISABLE | PORT_CTRL_TX_DISABLE; - else if (i == B53_CPU_PORT) - port_ctrl = PORT_CTRL_RX_BCST_EN | - PORT_CTRL_RX_MCST_EN | - PORT_CTRL_RX_UCST_EN; - else - port_ctrl = 0; - - b53_write16(dev, B53_PVLAN_PAGE, B53_PVLAN_PORT_MASK(i), - pvlan_mask); - - /* port state is handled by bcm63xx_enet driver */ - if (!is63xx(dev) && !(is5301x(dev) && i == 6)) - b53_write8(dev, B53_CTRL_PAGE, B53_PORT_CTRL(i), - port_ctrl); - } -} - -static void b53_enable_mib(struct b53_device *dev) -{ - u8 gc; - - b53_read8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); - - gc &= ~(GC_RESET_MIB | GC_MIB_AC_EN); - - b53_write8(dev, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc); -} - -static int b53_apply(struct b53_device *dev) -{ - int i; - - /* clear all vlan entries */ - if (is5325(dev) || is5365(dev)) { - for (i = 1; i < dev->sw_dev.vlans; i++) - b53_set_vlan_entry(dev, i, 0, 0); - } else { - b53_do_vlan_op(dev, VTA_CMD_CLEAR); - } - - b53_enable_vlan(dev, dev->enable_vlan); - - /* fill VLAN table */ - if (dev->enable_vlan) { - for (i = 0; i < dev->sw_dev.vlans; i++) { - struct b53_vlan *vlan = &dev->vlans[i]; - - if (!vlan->members) - continue; - - b53_set_vlan_entry(dev, i, vlan->members, vlan->untag); - } - - b53_for_each_port(dev, i) - b53_write16(dev, B53_VLAN_PAGE, - B53_VLAN_PORT_DEF_TAG(i), - dev->ports[i].pvid); - } else { - b53_for_each_port(dev, i) - b53_write16(dev, B53_VLAN_PAGE, - B53_VLAN_PORT_DEF_TAG(i), 1); - - } - - b53_enable_ports(dev); - - if (!is5325(dev) && !is5365(dev)) - b53_set_jumbo(dev, dev->enable_jumbo, 1); - - return 0; -} - -static void b53_switch_reset_gpio(struct b53_device *dev) -{ - int gpio = dev->reset_gpio; - - if (gpio < 0) - return; - - /* - * Reset sequence: RESET low(50ms)->high(20ms) - */ - gpio_set_value(gpio, 0); - mdelay(50); - - gpio_set_value(gpio, 1); - mdelay(20); - - dev->current_page = 0xff; -} - -static int b53_configure_ports_of(struct b53_device *dev) -{ - struct device_node *dn, *pn; - u32 port_num; - - dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); - - for_each_available_child_of_node(dn, pn) { - struct device_node *fixed_link; - - if (of_property_read_u32(pn, "reg", &port_num)) - continue; - - if (port_num > B53_CPU_PORT) - continue; - - fixed_link = of_get_child_by_name(pn, "fixed-link"); - if (fixed_link) { - u32 spd; - u8 po = GMII_PO_LINK; - int mode = of_get_phy_mode(pn); - - if (!of_property_read_u32(fixed_link, "speed", &spd)) { - switch (spd) { - case 10: - po |= GMII_PO_SPEED_10M; - break; - case 100: - po |= GMII_PO_SPEED_100M; - break; - case 2000: - if (is_imp_port(dev, port_num)) - po |= PORT_OVERRIDE_SPEED_2000M; - else - po |= GMII_PO_SPEED_2000M; - /* fall through */ - case 1000: - po |= GMII_PO_SPEED_1000M; - break; - } - } - - if (of_property_read_bool(fixed_link, "full-duplex")) - po |= PORT_OVERRIDE_FULL_DUPLEX; - if (of_property_read_bool(fixed_link, "pause")) - po |= GMII_PO_RX_FLOW; - if (of_property_read_bool(fixed_link, "asym-pause")) - po |= GMII_PO_TX_FLOW; - - if (is_imp_port(dev, port_num)) { - po |= PORT_OVERRIDE_EN; - - if (is5325(dev) && - mode == PHY_INTERFACE_MODE_REVMII) - po |= PORT_OVERRIDE_RV_MII_25; - - b53_write8(dev, B53_CTRL_PAGE, - B53_PORT_OVERRIDE_CTRL, po); - - if (is5325(dev) && - mode == PHY_INTERFACE_MODE_REVMII) { - b53_read8(dev, B53_CTRL_PAGE, - B53_PORT_OVERRIDE_CTRL, &po); - if (!(po & PORT_OVERRIDE_RV_MII_25)) - pr_err("Failed to enable reverse MII mode\n"); - return -EINVAL; - } - } else { - po |= GMII_PO_EN; - b53_write8(dev, B53_CTRL_PAGE, - B53_GMII_PORT_OVERRIDE_CTRL(port_num), - po); - } - } - } - - return 0; -} - -static int b53_configure_ports(struct b53_device *dev) -{ - u8 cpu_port = dev->sw_dev.cpu_port; - - /* configure MII port if necessary */ - if (is5325(dev)) { - u8 mii_port_override; - - b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - &mii_port_override); - /* reverse mii needs to be enabled */ - if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { - b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - mii_port_override | PORT_OVERRIDE_RV_MII_25); - b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - &mii_port_override); - - if (!(mii_port_override & PORT_OVERRIDE_RV_MII_25)) { - pr_err("Failed to enable reverse MII mode\n"); - return -EINVAL; - } - } - } else if (is531x5(dev) && cpu_port == B53_CPU_PORT) { - u8 mii_port_override; - - b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - &mii_port_override); - b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - mii_port_override | PORT_OVERRIDE_EN | - PORT_OVERRIDE_LINK); - - /* BCM47189 has another interface connected to the port 5 */ - if (dev->enabled_ports & BIT(5)) { - u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(5); - u8 gmii_po; - - b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); - gmii_po |= GMII_PO_LINK | - GMII_PO_RX_FLOW | - GMII_PO_TX_FLOW | - GMII_PO_EN; - b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); - } - } else if (is5301x(dev)) { - if (cpu_port == 8) { - u8 mii_port_override; - - b53_read8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - &mii_port_override); - mii_port_override |= PORT_OVERRIDE_LINK | - PORT_OVERRIDE_RX_FLOW | - PORT_OVERRIDE_TX_FLOW | - PORT_OVERRIDE_SPEED_2000M | - PORT_OVERRIDE_EN; - b53_write8(dev, B53_CTRL_PAGE, B53_PORT_OVERRIDE_CTRL, - mii_port_override); - - /* TODO: Ports 5 & 7 require some extra handling */ - } else { - u8 po_reg = B53_GMII_PORT_OVERRIDE_CTRL(cpu_port); - u8 gmii_po; - - b53_read8(dev, B53_CTRL_PAGE, po_reg, &gmii_po); - gmii_po |= GMII_PO_LINK | - GMII_PO_RX_FLOW | - GMII_PO_TX_FLOW | - GMII_PO_EN | - GMII_PO_SPEED_2000M; - b53_write8(dev, B53_CTRL_PAGE, po_reg, gmii_po); - } - } - - return 0; -} - -static int b53_switch_reset(struct b53_device *dev) -{ - int ret = 0; - u8 mgmt; - - b53_switch_reset_gpio(dev); - - if (is539x(dev)) { - b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x83); - b53_write8(dev, B53_CTRL_PAGE, B53_SOFTRESET, 0x00); - } - - b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); - - if (!(mgmt & SM_SW_FWD_EN)) { - mgmt &= ~SM_SW_FWD_MODE; - mgmt |= SM_SW_FWD_EN; - - b53_write8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, mgmt); - b53_read8(dev, B53_CTRL_PAGE, B53_SWITCH_MODE, &mgmt); - - if (!(mgmt & SM_SW_FWD_EN)) { - pr_err("Failed to enable switch!\n"); - return -EINVAL; - } - } - - /* enable all ports */ - b53_enable_ports(dev); - - if (dev->dev->of_node) - ret = b53_configure_ports_of(dev); - else - ret = b53_configure_ports(dev); - - if (ret) - return ret; - - b53_enable_mib(dev); - - return b53_flush_arl(dev); -} - -/* - * Swconfig glue functions - */ - -static int b53_global_get_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - val->value.i = priv->enable_vlan; - - return 0; -} - -static int b53_global_set_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - priv->enable_vlan = val->value.i; - - return 0; -} - -static int b53_global_get_jumbo_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - val->value.i = priv->enable_jumbo; - - return 0; -} - -static int b53_global_set_jumbo_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - priv->enable_jumbo = val->value.i; - - return 0; -} - -static int b53_global_get_4095_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - val->value.i = priv->allow_vid_4095; - - return 0; -} - -static int b53_global_set_4095_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - priv->allow_vid_4095 = val->value.i; - - return 0; -} - -static int b53_global_get_ports(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - val->len = snprintf(priv->buf, B53_BUF_SIZE, "0x%04x", - priv->enabled_ports); - val->value.s = priv->buf; - - return 0; -} - -static int b53_port_get_pvid(struct switch_dev *dev, int port, int *val) -{ - struct b53_device *priv = sw_to_b53(dev); - - *val = priv->ports[port].pvid; - - return 0; -} - -static int b53_port_set_pvid(struct switch_dev *dev, int port, int val) -{ - struct b53_device *priv = sw_to_b53(dev); - - if (val > 15 && is5325(priv)) - return -EINVAL; - if (val == 4095 && !priv->allow_vid_4095) - return -EINVAL; - - priv->ports[port].pvid = val; - - return 0; -} - -static int b53_vlan_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - struct switch_port *port = &val->value.ports[0]; - struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; - int i; - - val->len = 0; - - if (!vlan->members) - return 0; - - for (i = 0; i < dev->ports; i++) { - if (!(vlan->members & BIT(i))) - continue; - - - if (!(vlan->untag & BIT(i))) - port->flags = BIT(SWITCH_PORT_FLAG_TAGGED); - else - port->flags = 0; - - port->id = i; - val->len++; - port++; - } - - return 0; -} - -static int b53_vlan_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - struct switch_port *port; - struct b53_vlan *vlan = &priv->vlans[val->port_vlan]; - int i; - - /* only BCM5325 and BCM5365 supports VID 0 */ - if (val->port_vlan == 0 && !is5325(priv) && !is5365(priv)) - return -EINVAL; - - /* VLAN 4095 needs special handling */ - if (val->port_vlan == 4095 && !priv->allow_vid_4095) - return -EINVAL; - - port = &val->value.ports[0]; - vlan->members = 0; - vlan->untag = 0; - for (i = 0; i < val->len; i++, port++) { - vlan->members |= BIT(port->id); - - if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) { - vlan->untag |= BIT(port->id); - priv->ports[port->id].pvid = val->port_vlan; - }; - } - - /* ignore disabled ports */ - vlan->members &= priv->enabled_ports; - vlan->untag &= priv->enabled_ports; - - return 0; -} - -static int b53_port_get_link(struct switch_dev *dev, int port, - struct switch_port_link *link) -{ - struct b53_device *priv = sw_to_b53(dev); - - if (is_cpu_port(priv, port)) { - link->link = 1; - link->duplex = 1; - link->speed = is5325(priv) || is5365(priv) ? - SWITCH_PORT_SPEED_100 : SWITCH_PORT_SPEED_1000; - link->aneg = 0; - } else if (priv->enabled_ports & BIT(port)) { - u32 speed; - u16 lnk, duplex; - - b53_read16(priv, B53_STAT_PAGE, B53_LINK_STAT, &lnk); - b53_read16(priv, B53_STAT_PAGE, priv->duplex_reg, &duplex); - - lnk = (lnk >> port) & 1; - duplex = (duplex >> port) & 1; - - if (is5325(priv) || is5365(priv)) { - u16 tmp; - - b53_read16(priv, B53_STAT_PAGE, B53_SPEED_STAT, &tmp); - speed = SPEED_PORT_FE(tmp, port); - } else { - b53_read32(priv, B53_STAT_PAGE, B53_SPEED_STAT, &speed); - speed = SPEED_PORT_GE(speed, port); - } - - link->link = lnk; - if (lnk) { - link->duplex = duplex; - switch (speed) { - case SPEED_STAT_10M: - link->speed = SWITCH_PORT_SPEED_10; - break; - case SPEED_STAT_100M: - link->speed = SWITCH_PORT_SPEED_100; - break; - case SPEED_STAT_1000M: - link->speed = SWITCH_PORT_SPEED_1000; - break; - } - } - - link->aneg = 1; - } else { - link->link = 0; - } - - return 0; - -} - -static int b53_port_set_link(struct switch_dev *sw_dev, int port, - struct switch_port_link *link) -{ - struct b53_device *dev = sw_to_b53(sw_dev); - - /* - * TODO: BCM63XX requires special handling as it can have external phys - * and ports might be GE or only FE - */ - if (is63xx(dev)) - return -ENOTSUPP; - - if (port == sw_dev->cpu_port) - return -EINVAL; - - if (!(BIT(port) & dev->enabled_ports)) - return -EINVAL; - - if (link->speed == SWITCH_PORT_SPEED_1000 && - (is5325(dev) || is5365(dev))) - return -EINVAL; - - if (link->speed == SWITCH_PORT_SPEED_1000 && !link->duplex) - return -EINVAL; - - return switch_generic_set_link(sw_dev, port, link); -} - -static int b53_phy_read16(struct switch_dev *dev, int addr, u8 reg, u16 *value) -{ - struct b53_device *priv = sw_to_b53(dev); - - if (priv->ops->phy_read16) - return priv->ops->phy_read16(priv, addr, reg, value); - - return b53_read16(priv, B53_PORT_MII_PAGE(addr), reg, value); -} - -static int b53_phy_write16(struct switch_dev *dev, int addr, u8 reg, u16 value) -{ - struct b53_device *priv = sw_to_b53(dev); - - if (priv->ops->phy_write16) - return priv->ops->phy_write16(priv, addr, reg, value); - - return b53_write16(priv, B53_PORT_MII_PAGE(addr), reg, value); -} - -static int b53_global_reset_switch(struct switch_dev *dev) -{ - struct b53_device *priv = sw_to_b53(dev); - - /* reset vlans */ - priv->enable_vlan = 0; - priv->enable_jumbo = 0; - priv->allow_vid_4095 = 0; - - memset(priv->vlans, 0, sizeof(*priv->vlans) * dev->vlans); - memset(priv->ports, 0, sizeof(*priv->ports) * dev->ports); - - return b53_switch_reset(priv); -} - -static int b53_global_apply_config(struct switch_dev *dev) -{ - struct b53_device *priv = sw_to_b53(dev); - - /* disable switching */ - b53_set_forwarding(priv, 0); - - b53_apply(priv); - - /* enable switching */ - b53_set_forwarding(priv, 1); - - return 0; -} - - -static int b53_global_reset_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *priv = sw_to_b53(dev); - u8 gc; - - b53_read8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, &gc); - - b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc | GC_RESET_MIB); - mdelay(1); - b53_write8(priv, B53_MGMT_PAGE, B53_GLOBAL_CONFIG, gc & ~GC_RESET_MIB); - mdelay(1); - - return 0; -} - -static int b53_port_get_mib(struct switch_dev *sw_dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct b53_device *dev = sw_to_b53(sw_dev); - const struct b53_mib_desc *mibs; - int port = val->port_vlan; - int len = 0; - - if (!(BIT(port) & dev->enabled_ports)) - return -1; - - if (is5365(dev)) { - if (port == 5) - port = 8; - - mibs = b53_mibs_65; - } else if (is63xx(dev)) { - mibs = b53_mibs_63xx; - } else { - mibs = b53_mibs; - } - - dev->buf[0] = 0; - - for (; mibs->size > 0; mibs++) { - u64 val; - - if (mibs->size == 8) { - b53_read64(dev, B53_MIB_PAGE(port), mibs->offset, &val); - } else { - u32 val32; - - b53_read32(dev, B53_MIB_PAGE(port), mibs->offset, - &val32); - val = val32; - } - - len += snprintf(dev->buf + len, B53_BUF_SIZE - len, - "%-20s: %llu\n", mibs->name, val); - } - - val->len = len; - val->value.s = dev->buf; - - return 0; -} - -static int b53_port_get_stats(struct switch_dev *sw_dev, int port, - struct switch_port_stats *stats) -{ - struct b53_device *dev = sw_to_b53(sw_dev); - const struct b53_mib_desc *mibs; - int txb_id, rxb_id; - u64 rxb, txb; - - if (!(BIT(port) & dev->enabled_ports)) - return -EINVAL; - - txb_id = B53XX_MIB_TXB_ID; - rxb_id = B53XX_MIB_RXB_ID; - - if (is5365(dev)) { - if (port == 5) - port = 8; - - mibs = b53_mibs_65; - } else if (is63xx(dev)) { - mibs = b53_mibs_63xx; - txb_id = B63XX_MIB_TXB_ID; - rxb_id = B63XX_MIB_RXB_ID; - } else { - mibs = b53_mibs; - } - - dev->buf[0] = 0; - - if (mibs->size == 8) { - b53_read64(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &txb); - b53_read64(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &rxb); - } else { - u32 val32; - - b53_read32(dev, B53_MIB_PAGE(port), mibs[txb_id].offset, &val32); - txb = val32; - - b53_read32(dev, B53_MIB_PAGE(port), mibs[rxb_id].offset, &val32); - rxb = val32; - } - - stats->tx_bytes = txb; - stats->rx_bytes = rxb; - - return 0; -} - -static struct switch_attr b53_global_ops_25[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = b53_global_set_vlan_enable, - .get = b53_global_get_vlan_enable, - .max = 1, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "ports", - .description = "Available ports (as bitmask)", - .get = b53_global_get_ports, - }, -}; - -static struct switch_attr b53_global_ops_65[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = b53_global_set_vlan_enable, - .get = b53_global_get_vlan_enable, - .max = 1, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "ports", - .description = "Available ports (as bitmask)", - .get = b53_global_get_ports, - }, - { - .type = SWITCH_TYPE_INT, - .name = "reset_mib", - .description = "Reset MIB counters", - .set = b53_global_reset_mib, - }, -}; - -static struct switch_attr b53_global_ops[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = b53_global_set_vlan_enable, - .get = b53_global_get_vlan_enable, - .max = 1, - }, - { - .type = SWITCH_TYPE_STRING, - .name = "ports", - .description = "Available Ports (as bitmask)", - .get = b53_global_get_ports, - }, - { - .type = SWITCH_TYPE_INT, - .name = "reset_mib", - .description = "Reset MIB counters", - .set = b53_global_reset_mib, - }, - { - .type = SWITCH_TYPE_INT, - .name = "enable_jumbo", - .description = "Enable Jumbo Frames", - .set = b53_global_set_jumbo_enable, - .get = b53_global_get_jumbo_enable, - .max = 1, - }, - { - .type = SWITCH_TYPE_INT, - .name = "allow_vid_4095", - .description = "Allow VID 4095", - .set = b53_global_set_4095_enable, - .get = b53_global_get_4095_enable, - .max = 1, - }, -}; - -static struct switch_attr b53_port_ops[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get port's MIB counters", - .get = b53_port_get_mib, - }, -}; - -static struct switch_attr b53_no_ops[] = { -}; - -static const struct switch_dev_ops b53_switch_ops_25 = { - .attr_global = { - .attr = b53_global_ops_25, - .n_attr = ARRAY_SIZE(b53_global_ops_25), - }, - .attr_port = { - .attr = b53_no_ops, - .n_attr = ARRAY_SIZE(b53_no_ops), - }, - .attr_vlan = { - .attr = b53_no_ops, - .n_attr = ARRAY_SIZE(b53_no_ops), - }, - - .get_vlan_ports = b53_vlan_get_ports, - .set_vlan_ports = b53_vlan_set_ports, - .get_port_pvid = b53_port_get_pvid, - .set_port_pvid = b53_port_set_pvid, - .apply_config = b53_global_apply_config, - .reset_switch = b53_global_reset_switch, - .get_port_link = b53_port_get_link, - .set_port_link = b53_port_set_link, - .get_port_stats = b53_port_get_stats, - .phy_read16 = b53_phy_read16, - .phy_write16 = b53_phy_write16, -}; - -static const struct switch_dev_ops b53_switch_ops_65 = { - .attr_global = { - .attr = b53_global_ops_65, - .n_attr = ARRAY_SIZE(b53_global_ops_65), - }, - .attr_port = { - .attr = b53_port_ops, - .n_attr = ARRAY_SIZE(b53_port_ops), - }, - .attr_vlan = { - .attr = b53_no_ops, - .n_attr = ARRAY_SIZE(b53_no_ops), - }, - - .get_vlan_ports = b53_vlan_get_ports, - .set_vlan_ports = b53_vlan_set_ports, - .get_port_pvid = b53_port_get_pvid, - .set_port_pvid = b53_port_set_pvid, - .apply_config = b53_global_apply_config, - .reset_switch = b53_global_reset_switch, - .get_port_link = b53_port_get_link, - .set_port_link = b53_port_set_link, - .get_port_stats = b53_port_get_stats, - .phy_read16 = b53_phy_read16, - .phy_write16 = b53_phy_write16, -}; - -static const struct switch_dev_ops b53_switch_ops = { - .attr_global = { - .attr = b53_global_ops, - .n_attr = ARRAY_SIZE(b53_global_ops), - }, - .attr_port = { - .attr = b53_port_ops, - .n_attr = ARRAY_SIZE(b53_port_ops), - }, - .attr_vlan = { - .attr = b53_no_ops, - .n_attr = ARRAY_SIZE(b53_no_ops), - }, - - .get_vlan_ports = b53_vlan_get_ports, - .set_vlan_ports = b53_vlan_set_ports, - .get_port_pvid = b53_port_get_pvid, - .set_port_pvid = b53_port_set_pvid, - .apply_config = b53_global_apply_config, - .reset_switch = b53_global_reset_switch, - .get_port_link = b53_port_get_link, - .set_port_link = b53_port_set_link, - .get_port_stats = b53_port_get_stats, - .phy_read16 = b53_phy_read16, - .phy_write16 = b53_phy_write16, -}; - -struct b53_chip_data { - u32 chip_id; - const char *dev_name; - const char *alias; - u16 vlans; - u16 enabled_ports; - u8 cpu_port; - u8 vta_regs[3]; - u8 duplex_reg; - u8 jumbo_pm_reg; - u8 jumbo_size_reg; - const struct switch_dev_ops *sw_ops; -}; - -#define B53_VTA_REGS \ - { B53_VT_ACCESS, B53_VT_INDEX, B53_VT_ENTRY } -#define B53_VTA_REGS_9798 \ - { B53_VT_ACCESS_9798, B53_VT_INDEX_9798, B53_VT_ENTRY_9798 } -#define B53_VTA_REGS_63XX \ - { B53_VT_ACCESS_63XX, B53_VT_INDEX_63XX, B53_VT_ENTRY_63XX } - -static const struct b53_chip_data b53_switch_chips[] = { - { - .chip_id = BCM5325_DEVICE_ID, - .dev_name = "BCM5325", - .alias = "bcm5325", - .vlans = 16, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT_25, - .duplex_reg = B53_DUPLEX_STAT_FE, - .sw_ops = &b53_switch_ops_25, - }, - { - .chip_id = BCM5365_DEVICE_ID, - .dev_name = "BCM5365", - .alias = "bcm5365", - .vlans = 256, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT_25, - .duplex_reg = B53_DUPLEX_STAT_FE, - .sw_ops = &b53_switch_ops_65, - }, - { - .chip_id = BCM5395_DEVICE_ID, - .dev_name = "BCM5395", - .alias = "bcm5395", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM5397_DEVICE_ID, - .dev_name = "BCM5397", - .alias = "bcm5397", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS_9798, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM5398_DEVICE_ID, - .dev_name = "BCM5398", - .alias = "bcm5398", - .vlans = 4096, - .enabled_ports = 0x7f, - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS_9798, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53115_DEVICE_ID, - .dev_name = "BCM53115", - .alias = "bcm53115", - .vlans = 4096, - .enabled_ports = 0x1f, - .vta_regs = B53_VTA_REGS, - .cpu_port = B53_CPU_PORT, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53125_DEVICE_ID, - .dev_name = "BCM53125", - .alias = "bcm53125", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53128_DEVICE_ID, - .dev_name = "BCM53128", - .alias = "bcm53128", - .vlans = 4096, - .enabled_ports = 0x1ff, - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM63XX_DEVICE_ID, - .dev_name = "BCM63xx", - .alias = "bcm63xx", - .vlans = 4096, - .enabled_ports = 0, /* pdata must provide them */ - .cpu_port = B53_CPU_PORT, - .vta_regs = B53_VTA_REGS_63XX, - .duplex_reg = B53_DUPLEX_STAT_63XX, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK_63XX, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE_63XX, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53010_DEVICE_ID, - .dev_name = "BCM53010", - .alias = "bcm53011", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53011_DEVICE_ID, - .dev_name = "BCM53011", - .alias = "bcm53011", - .vlans = 4096, - .enabled_ports = 0x1bf, - .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53012_DEVICE_ID, - .dev_name = "BCM53012", - .alias = "bcm53011", - .vlans = 4096, - .enabled_ports = 0x1bf, - .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53018_DEVICE_ID, - .dev_name = "BCM53018", - .alias = "bcm53018", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, - { - .chip_id = BCM53019_DEVICE_ID, - .dev_name = "BCM53019", - .alias = "bcm53019", - .vlans = 4096, - .enabled_ports = 0x1f, - .cpu_port = B53_CPU_PORT_25, /* TODO: auto detect */ - .vta_regs = B53_VTA_REGS, - .duplex_reg = B53_DUPLEX_STAT_GE, - .jumbo_pm_reg = B53_JUMBO_PORT_MASK, - .jumbo_size_reg = B53_JUMBO_MAX_SIZE, - .sw_ops = &b53_switch_ops, - }, -}; - -static int b53_switch_init_of(struct b53_device *dev) -{ - struct device_node *dn, *pn; - const char *alias; - u32 port_num; - u16 ports = 0; - - dn = of_get_child_by_name(dev_of_node(dev->dev), "ports"); - if (!dn) - return -EINVAL; - - for_each_available_child_of_node(dn, pn) { - const char *label; - int len; - - if (of_property_read_u32(pn, "reg", &port_num)) - continue; - - if (port_num > B53_CPU_PORT) - continue; - - ports |= BIT(port_num); - - label = of_get_property(pn, "label", &len); - if (label && !strcmp(label, "cpu")) - dev->sw_dev.cpu_port = port_num; - } - - dev->enabled_ports = ports; - - if (!of_property_read_string(dev_of_node(dev->dev), "lede,alias", - &alias)) - dev->sw_dev.alias = devm_kstrdup(dev->dev, alias, GFP_KERNEL); - - return 0; -} - -static int b53_switch_init(struct b53_device *dev) -{ - struct switch_dev *sw_dev = &dev->sw_dev; - unsigned i; - int ret; - - for (i = 0; i < ARRAY_SIZE(b53_switch_chips); i++) { - const struct b53_chip_data *chip = &b53_switch_chips[i]; - - if (chip->chip_id == dev->chip_id) { - sw_dev->name = chip->dev_name; - if (!sw_dev->alias) - sw_dev->alias = chip->alias; - if (!dev->enabled_ports) - dev->enabled_ports = chip->enabled_ports; - dev->duplex_reg = chip->duplex_reg; - dev->vta_regs[0] = chip->vta_regs[0]; - dev->vta_regs[1] = chip->vta_regs[1]; - dev->vta_regs[2] = chip->vta_regs[2]; - dev->jumbo_pm_reg = chip->jumbo_pm_reg; - sw_dev->ops = chip->sw_ops; - sw_dev->cpu_port = chip->cpu_port; - sw_dev->vlans = chip->vlans; - break; - } - } - - if (!sw_dev->name) - return -EINVAL; - - /* check which BCM5325x version we have */ - if (is5325(dev)) { - u8 vc4; - - b53_read8(dev, B53_VLAN_PAGE, B53_VLAN_CTRL4_25, &vc4); - - /* check reserved bits */ - switch (vc4 & 3) { - case 1: - /* BCM5325E */ - break; - case 3: - /* BCM5325F - do not use port 4 */ - dev->enabled_ports &= ~BIT(4); - break; - default: -/* On the BCM47XX SoCs this is the supported internal switch.*/ -#ifndef CONFIG_BCM47XX - /* BCM5325M */ - return -EINVAL; -#else - break; -#endif - } - } else if (dev->chip_id == BCM53115_DEVICE_ID) { - u64 strap_value; - - b53_read48(dev, B53_STAT_PAGE, B53_STRAP_VALUE, &strap_value); - /* use second IMP port if GMII is enabled */ - if (strap_value & SV_GMII_CTRL_115) - sw_dev->cpu_port = 5; - } - - if (dev_of_node(dev->dev)) { - ret = b53_switch_init_of(dev); - if (ret) - return ret; - } - - dev->enabled_ports |= BIT(sw_dev->cpu_port); - sw_dev->ports = fls(dev->enabled_ports); - - dev->ports = devm_kzalloc(dev->dev, - sizeof(struct b53_port) * sw_dev->ports, - GFP_KERNEL); - if (!dev->ports) - return -ENOMEM; - - dev->vlans = devm_kzalloc(dev->dev, - sizeof(struct b53_vlan) * sw_dev->vlans, - GFP_KERNEL); - if (!dev->vlans) - return -ENOMEM; - - dev->buf = devm_kzalloc(dev->dev, B53_BUF_SIZE, GFP_KERNEL); - if (!dev->buf) - return -ENOMEM; - - dev->reset_gpio = b53_switch_get_reset_gpio(dev); - if (dev->reset_gpio >= 0) { - ret = devm_gpio_request_one(dev->dev, dev->reset_gpio, - GPIOF_OUT_INIT_HIGH, "robo_reset"); - if (ret) - return ret; - } - - return b53_switch_reset(dev); -} - -struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, - void *priv) -{ - struct b53_device *dev; - - dev = devm_kzalloc(base, sizeof(*dev), GFP_KERNEL); - if (!dev) - return NULL; - - dev->dev = base; - dev->ops = ops; - dev->priv = priv; - mutex_init(&dev->reg_mutex); - - return dev; -} -EXPORT_SYMBOL(b53_switch_alloc); - -int b53_switch_detect(struct b53_device *dev) -{ - u32 id32; - u16 tmp; - u8 id8; - int ret; - - ret = b53_read8(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id8); - if (ret) - return ret; - - switch (id8) { - case 0: - /* - * BCM5325 and BCM5365 do not have this register so reads - * return 0. But the read operation did succeed, so assume - * this is one of them. - * - * Next check if we can write to the 5325's VTA register; for - * 5365 it is read only. - */ - - b53_write16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, 0xf); - b53_read16(dev, B53_VLAN_PAGE, B53_VLAN_TABLE_ACCESS_25, &tmp); - - if (tmp == 0xf) - dev->chip_id = BCM5325_DEVICE_ID; - else - dev->chip_id = BCM5365_DEVICE_ID; - break; - case BCM5395_DEVICE_ID: - case BCM5397_DEVICE_ID: - case BCM5398_DEVICE_ID: - dev->chip_id = id8; - break; - default: - ret = b53_read32(dev, B53_MGMT_PAGE, B53_DEVICE_ID, &id32); - if (ret) - return ret; - - switch (id32) { - case BCM53115_DEVICE_ID: - case BCM53125_DEVICE_ID: - case BCM53128_DEVICE_ID: - case BCM53010_DEVICE_ID: - case BCM53011_DEVICE_ID: - case BCM53012_DEVICE_ID: - case BCM53018_DEVICE_ID: - case BCM53019_DEVICE_ID: - dev->chip_id = id32; - break; - default: - pr_err("unsupported switch detected (BCM53%02x/BCM%x)\n", - id8, id32); - return -ENODEV; - } - } - - if (dev->chip_id == BCM5325_DEVICE_ID) - return b53_read8(dev, B53_STAT_PAGE, B53_REV_ID_25, - &dev->core_rev); - else - return b53_read8(dev, B53_MGMT_PAGE, B53_REV_ID, - &dev->core_rev); -} -EXPORT_SYMBOL(b53_switch_detect); - -int b53_switch_register(struct b53_device *dev) -{ - int ret; - - if (dev->pdata) { - dev->chip_id = dev->pdata->chip_id; - dev->enabled_ports = dev->pdata->enabled_ports; - dev->sw_dev.alias = dev->pdata->alias; - } - - if (!dev->chip_id && b53_switch_detect(dev)) - return -EINVAL; - - ret = b53_switch_init(dev); - if (ret) - return ret; - - pr_info("found switch: %s, rev %i\n", dev->sw_dev.name, dev->core_rev); - - return register_switch(&dev->sw_dev, NULL); -} -EXPORT_SYMBOL(b53_switch_register); - -MODULE_AUTHOR("Jonas Gorski "); -MODULE_DESCRIPTION("B53 switch library"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c b/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c deleted file mode 100644 index 75bb4d941c..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c +++ /dev/null @@ -1,436 +0,0 @@ -/* - * B53 register access through MII registers - * - * Copyright (C) 2011-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -#include "b53_priv.h" - -#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ - -/* MII registers */ -#define REG_MII_PAGE 0x10 /* MII Page register */ -#define REG_MII_ADDR 0x11 /* MII Address register */ -#define REG_MII_DATA0 0x18 /* MII Data register 0 */ -#define REG_MII_DATA1 0x19 /* MII Data register 1 */ -#define REG_MII_DATA2 0x1a /* MII Data register 2 */ -#define REG_MII_DATA3 0x1b /* MII Data register 3 */ - -#define REG_MII_PAGE_ENABLE BIT(0) -#define REG_MII_ADDR_WRITE BIT(0) -#define REG_MII_ADDR_READ BIT(1) - -static int b53_mdio_op(struct b53_device *dev, u8 page, u8 reg, u16 op) -{ - int i; - u16 v; - int ret; - struct mii_bus *bus = dev->priv; - - if (dev->current_page != page) { - /* set page number */ - v = (page << 8) | REG_MII_PAGE_ENABLE; - ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_PAGE, v); - if (ret) - return ret; - dev->current_page = page; - } - - /* set register address */ - v = (reg << 8) | op; - ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_ADDR, v); - if (ret) - return ret; - - /* check if operation completed */ - for (i = 0; i < 5; ++i) { - v = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_ADDR); - if (!(v & (REG_MII_ADDR_WRITE | REG_MII_ADDR_READ))) - break; - usleep_range(10, 100); - } - - if (WARN_ON(i == 5)) - return -EIO; - - return 0; -} - -static int b53_mdio_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) -{ - struct mii_bus *bus = dev->priv; - int ret; - - ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); - if (ret) - return ret; - - *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0) & 0xff; - - return 0; -} - -static int b53_mdio_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) -{ - struct mii_bus *bus = dev->priv; - int ret; - - ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); - if (ret) - return ret; - - *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); - - return 0; -} - -static int b53_mdio_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) -{ - struct mii_bus *bus = dev->priv; - int ret; - - ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); - if (ret) - return ret; - - *val = mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0); - *val |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA1) << 16; - - return 0; -} - -static int b53_mdio_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - struct mii_bus *bus = dev->priv; - u64 temp = 0; - int i; - int ret; - - ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); - if (ret) - return ret; - - for (i = 2; i >= 0; i--) { - temp <<= 16; - temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); - } - - *val = temp; - - return 0; -} - -static int b53_mdio_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - struct mii_bus *bus = dev->priv; - u64 temp = 0; - int i; - int ret; - - ret = b53_mdio_op(dev, page, reg, REG_MII_ADDR_READ); - if (ret) - return ret; - - for (i = 3; i >= 0; i--) { - temp <<= 16; - temp |= mdiobus_read(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i); - } - - *val = temp; - - return 0; -} - -static int b53_mdio_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) -{ - struct mii_bus *bus = dev->priv; - int ret; - - ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); - if (ret) - return ret; - - return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); -} - -static int b53_mdio_write16(struct b53_device *dev, u8 page, u8 reg, - u16 value) -{ - struct mii_bus *bus = dev->priv; - int ret; - - ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0, value); - if (ret) - return ret; - - return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); -} - -static int b53_mdio_write32(struct b53_device *dev, u8 page, u8 reg, - u32 value) -{ - struct mii_bus *bus = dev->priv; - unsigned int i; - u32 temp = value; - - for (i = 0; i < 2; i++) { - int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, - temp & 0xffff); - if (ret) - return ret; - temp >>= 16; - } - - return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); - -} - -static int b53_mdio_write48(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - struct mii_bus *bus = dev->priv; - unsigned i; - u64 temp = value; - - for (i = 0; i < 3; i++) { - int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, - temp & 0xffff); - if (ret) - return ret; - temp >>= 16; - } - - return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); - -} - -static int b53_mdio_write64(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - struct mii_bus *bus = dev->priv; - unsigned i; - u64 temp = value; - - for (i = 0; i < 4; i++) { - int ret = mdiobus_write(bus, B53_PSEUDO_PHY, REG_MII_DATA0 + i, - temp & 0xffff); - if (ret) - return ret; - temp >>= 16; - } - - return b53_mdio_op(dev, page, reg, REG_MII_ADDR_WRITE); -} - -static int b53_mdio_phy_read16(struct b53_device *dev, int addr, u8 reg, - u16 *value) -{ - struct mii_bus *bus = dev->priv; - - *value = mdiobus_read(bus, addr, reg); - - return 0; -} - -static int b53_mdio_phy_write16(struct b53_device *dev, int addr, u8 reg, - u16 value) -{ - struct mii_bus *bus = dev->priv; - - return mdiobus_write(bus, addr, reg, value); -} - -static struct b53_io_ops b53_mdio_ops = { - .read8 = b53_mdio_read8, - .read16 = b53_mdio_read16, - .read32 = b53_mdio_read32, - .read48 = b53_mdio_read48, - .read64 = b53_mdio_read64, - .write8 = b53_mdio_write8, - .write16 = b53_mdio_write16, - .write32 = b53_mdio_write32, - .write48 = b53_mdio_write48, - .write64 = b53_mdio_write64, - .phy_read16 = b53_mdio_phy_read16, - .phy_write16 = b53_mdio_phy_write16, -}; - -static int b53_phy_probe(struct phy_device *phydev) -{ - struct b53_device dev; - int ret; - - /* allow the generic phy driver to take over */ - if (phydev->mdio.addr != B53_PSEUDO_PHY && phydev->mdio.addr != 0) - return -ENODEV; - - dev.current_page = 0xff; - dev.priv = phydev->mdio.bus; - dev.ops = &b53_mdio_ops; - dev.pdata = NULL; - mutex_init(&dev.reg_mutex); - - ret = b53_switch_detect(&dev); - if (ret) - return ret; - - if (is5325(&dev) || is5365(&dev)) - phydev->supported = SUPPORTED_100baseT_Full; - else - phydev->supported = SUPPORTED_1000baseT_Full; - - phydev->advertising = phydev->supported; - - return 0; -} - -static int b53_phy_config_init(struct phy_device *phydev) -{ - struct b53_device *dev; - int ret; - - dev = b53_switch_alloc(&phydev->mdio.dev, &b53_mdio_ops, phydev->mdio.bus); - if (!dev) - return -ENOMEM; - - /* we don't use page 0xff, so force a page set */ - dev->current_page = 0xff; - /* force the ethX as alias */ - dev->sw_dev.alias = phydev->attached_dev->name; - - ret = b53_switch_register(dev); - if (ret) { - dev_err(dev->dev, "failed to register switch: %i\n", ret); - return ret; - } - - phydev->priv = dev; - - return 0; -} - -static void b53_phy_remove(struct phy_device *phydev) -{ - struct b53_device *priv = phydev->priv; - - if (!priv) - return; - - b53_switch_remove(priv); - - phydev->priv = NULL; -} - -static int b53_phy_config_aneg(struct phy_device *phydev) -{ - return 0; -} - -static int b53_phy_read_status(struct phy_device *phydev) -{ - struct b53_device *priv = phydev->priv; - - if (is5325(priv) || is5365(priv)) - phydev->speed = 100; - else - phydev->speed = 1000; - - phydev->duplex = DUPLEX_FULL; - phydev->link = 1; - phydev->state = PHY_RUNNING; - - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - - return 0; -} - -/* BCM5325, BCM539x */ -static struct phy_driver b53_phy_driver_id1 = { - .phy_id = 0x0143bc00, - .name = "Broadcom B53 (1)", - .phy_id_mask = 0x1ffffc00, - .features = 0, - .probe = b53_phy_probe, - .remove = b53_phy_remove, - .config_aneg = b53_phy_config_aneg, - .config_init = b53_phy_config_init, - .read_status = b53_phy_read_status, -}; - -/* BCM53125, BCM53128 */ -static struct phy_driver b53_phy_driver_id2 = { - .phy_id = 0x03625c00, - .name = "Broadcom B53 (2)", - .phy_id_mask = 0x1ffffc00, - .features = 0, - .probe = b53_phy_probe, - .remove = b53_phy_remove, - .config_aneg = b53_phy_config_aneg, - .config_init = b53_phy_config_init, - .read_status = b53_phy_read_status, -}; - -/* BCM5365 */ -static struct phy_driver b53_phy_driver_id3 = { - .phy_id = 0x00406000, - .name = "Broadcom B53 (3)", - .phy_id_mask = 0x1ffffc00, - .features = 0, - .probe = b53_phy_probe, - .remove = b53_phy_remove, - .config_aneg = b53_phy_config_aneg, - .config_init = b53_phy_config_init, - .read_status = b53_phy_read_status, -}; - -int __init b53_phy_driver_register(void) -{ - int ret; - - ret = phy_driver_register(&b53_phy_driver_id1, THIS_MODULE); - if (ret) - return ret; - - ret = phy_driver_register(&b53_phy_driver_id2, THIS_MODULE); - if (ret) - goto err1; - - ret = phy_driver_register(&b53_phy_driver_id3, THIS_MODULE); - if (!ret) - return 0; - - phy_driver_unregister(&b53_phy_driver_id2); -err1: - phy_driver_unregister(&b53_phy_driver_id1); - return ret; -} - -void __exit b53_phy_driver_unregister(void) -{ - phy_driver_unregister(&b53_phy_driver_id3); - phy_driver_unregister(&b53_phy_driver_id2); - phy_driver_unregister(&b53_phy_driver_id1); -} - -module_init(b53_phy_driver_register); -module_exit(b53_phy_driver_unregister); - -MODULE_DESCRIPTION("B53 MDIO access driver"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c b/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c deleted file mode 100644 index ab1895e9b5..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * B53 register access through memory mapped registers - * - * Copyright (C) 2012-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include - -#include "b53_priv.h" - -static int b53_mmap_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) -{ - u8 __iomem *regs = dev->priv; - - *val = readb(regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) -{ - u8 __iomem *regs = dev->priv; - - if (WARN_ON(reg % 2)) - return -EINVAL; - - if (dev->pdata && dev->pdata->big_endian) - *val = readw_be(regs + (page << 8) + reg); - else - *val = readw(regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) -{ - u8 __iomem *regs = dev->priv; - - if (WARN_ON(reg % 4)) - return -EINVAL; - - if (dev->pdata && dev->pdata->big_endian) - *val = readl_be(regs + (page << 8) + reg); - else - *val = readl(regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - if (WARN_ON(reg % 2)) - return -EINVAL; - - if (reg % 4) { - u16 lo; - u32 hi; - - b53_mmap_read16(dev, page, reg, &lo); - b53_mmap_read32(dev, page, reg + 2, &hi); - - *val = ((u64)hi << 16) | lo; - } else { - u32 lo; - u16 hi; - - b53_mmap_read32(dev, page, reg, &lo); - b53_mmap_read16(dev, page, reg + 4, &hi); - - *val = ((u64)hi << 32) | lo; - } - - return 0; -} - -static int b53_mmap_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - u32 hi, lo; - - if (WARN_ON(reg % 4)) - return -EINVAL; - - b53_mmap_read32(dev, page, reg, &lo); - b53_mmap_read32(dev, page, reg + 4, &hi); - - *val = ((u64)hi << 32) | lo; - - return 0; -} - -static int b53_mmap_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) -{ - u8 __iomem *regs = dev->priv; - - writeb(value, regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_write16(struct b53_device *dev, u8 page, u8 reg, - u16 value) -{ - u8 __iomem *regs = dev->priv; - - if (WARN_ON(reg % 2)) - return -EINVAL; - - if (dev->pdata && dev->pdata->big_endian) - writew_be(value, regs + (page << 8) + reg); - else - writew(value, regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_write32(struct b53_device *dev, u8 page, u8 reg, - u32 value) -{ - u8 __iomem *regs = dev->priv; - - if (WARN_ON(reg % 4)) - return -EINVAL; - - if (dev->pdata && dev->pdata->big_endian) - writel_be(value, regs + (page << 8) + reg); - else - writel(value, regs + (page << 8) + reg); - - return 0; -} - -static int b53_mmap_write48(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - if (WARN_ON(reg % 2)) - return -EINVAL; - - if (reg % 4) { - u32 hi = (u32)(value >> 16); - u16 lo = (u16)value; - - b53_mmap_write16(dev, page, reg, lo); - b53_mmap_write32(dev, page, reg + 2, hi); - } else { - u16 hi = (u16)(value >> 32); - u32 lo = (u32)value; - - b53_mmap_write32(dev, page, reg, lo); - b53_mmap_write16(dev, page, reg + 4, hi); - } - - return 0; -} - -static int b53_mmap_write64(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - u32 hi, lo; - - hi = (u32)(value >> 32); - lo = (u32)value; - - if (WARN_ON(reg % 4)) - return -EINVAL; - - b53_mmap_write32(dev, page, reg, lo); - b53_mmap_write32(dev, page, reg + 4, hi); - - return 0; -} - -static struct b53_io_ops b53_mmap_ops = { - .read8 = b53_mmap_read8, - .read16 = b53_mmap_read16, - .read32 = b53_mmap_read32, - .read48 = b53_mmap_read48, - .read64 = b53_mmap_read64, - .write8 = b53_mmap_write8, - .write16 = b53_mmap_write16, - .write32 = b53_mmap_write32, - .write48 = b53_mmap_write48, - .write64 = b53_mmap_write64, -}; - -static int b53_mmap_probe(struct platform_device *pdev) -{ - struct b53_platform_data *pdata = pdev->dev.platform_data; - struct b53_device *dev; - - if (!pdata) - return -EINVAL; - - dev = b53_switch_alloc(&pdev->dev, &b53_mmap_ops, pdata->regs); - if (!dev) - return -ENOMEM; - - if (pdata) - dev->pdata = pdata; - - platform_set_drvdata(pdev, dev); - - return b53_switch_register(dev); -} - -static int b53_mmap_remove(struct platform_device *pdev) -{ - struct b53_device *dev = platform_get_drvdata(pdev); - - if (dev) - b53_switch_remove(dev); - - return 0; -} - -static struct platform_driver b53_mmap_driver = { - .probe = b53_mmap_probe, - .remove = b53_mmap_remove, - .driver = { - .name = "b53-switch", - }, -}; - -module_platform_driver(b53_mmap_driver); -MODULE_AUTHOR("Jonas Gorski "); -MODULE_DESCRIPTION("B53 MMAP access driver"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_phy_fixup.c b/target/linux/generic/files/drivers/net/phy/b53/b53_phy_fixup.c deleted file mode 100644 index e2f8a39ab6..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_phy_fixup.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * B53 PHY Fixup call - * - * Copyright (C) 2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -#define B53_PSEUDO_PHY 0x1e /* Register Access Pseudo PHY */ - -#define B53_BRCM_OUI_1 0x0143bc00 -#define B53_BRCM_OUI_2 0x03625c00 -#define B53_BRCM_OUI_3 0x00406000 - -static int b53_phy_fixup(struct phy_device *dev) -{ - struct mii_bus *bus = dev->mdio.bus; - u32 phy_id; - - if (dev->mdio.addr != B53_PSEUDO_PHY) - return 0; - - /* read the first port's id */ - phy_id = mdiobus_read(bus, 0, 2) << 16; - phy_id |= mdiobus_read(bus, 0, 3); - - if ((phy_id & 0xfffffc00) == B53_BRCM_OUI_1 || - (phy_id & 0xfffffc00) == B53_BRCM_OUI_2 || - (phy_id & 0xfffffc00) == B53_BRCM_OUI_3) { - dev->phy_id = phy_id; - } - - return 0; -} - -int __init b53_phy_fixup_register(void) -{ - return phy_register_fixup_for_id(PHY_ANY_ID, b53_phy_fixup); -} - -subsys_initcall(b53_phy_fixup_register); diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h b/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h deleted file mode 100644 index a9296c9421..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h +++ /dev/null @@ -1,341 +0,0 @@ -/* - * B53 common definitions - * - * Copyright (C) 2011-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __B53_PRIV_H -#define __B53_PRIV_H - -#include -#include -#include - -struct b53_device; - -struct b53_io_ops { - int (*read8)(struct b53_device *dev, u8 page, u8 reg, u8 *value); - int (*read16)(struct b53_device *dev, u8 page, u8 reg, u16 *value); - int (*read32)(struct b53_device *dev, u8 page, u8 reg, u32 *value); - int (*read48)(struct b53_device *dev, u8 page, u8 reg, u64 *value); - int (*read64)(struct b53_device *dev, u8 page, u8 reg, u64 *value); - int (*write8)(struct b53_device *dev, u8 page, u8 reg, u8 value); - int (*write16)(struct b53_device *dev, u8 page, u8 reg, u16 value); - int (*write32)(struct b53_device *dev, u8 page, u8 reg, u32 value); - int (*write48)(struct b53_device *dev, u8 page, u8 reg, u64 value); - int (*write64)(struct b53_device *dev, u8 page, u8 reg, u64 value); - int (*phy_read16)(struct b53_device *dev, int addr, u8 reg, u16 *value); - int (*phy_write16)(struct b53_device *dev, int addr, u8 reg, u16 value); -}; - -enum { - BCM5325_DEVICE_ID = 0x25, - BCM5365_DEVICE_ID = 0x65, - BCM5395_DEVICE_ID = 0x95, - BCM5397_DEVICE_ID = 0x97, - BCM5398_DEVICE_ID = 0x98, - BCM53115_DEVICE_ID = 0x53115, - BCM53125_DEVICE_ID = 0x53125, - BCM53128_DEVICE_ID = 0x53128, - BCM63XX_DEVICE_ID = 0x6300, - BCM53010_DEVICE_ID = 0x53010, - BCM53011_DEVICE_ID = 0x53011, - BCM53012_DEVICE_ID = 0x53012, - BCM53018_DEVICE_ID = 0x53018, - BCM53019_DEVICE_ID = 0x53019, -}; - -#define B53_N_PORTS 9 -#define B53_N_PORTS_25 6 - -struct b53_vlan { - unsigned int members:B53_N_PORTS; - unsigned int untag:B53_N_PORTS; -}; - -struct b53_port { - unsigned int pvid:12; -}; - -struct b53_device { - struct switch_dev sw_dev; - struct b53_platform_data *pdata; - - struct mutex reg_mutex; - const struct b53_io_ops *ops; - - /* chip specific data */ - u32 chip_id; - u8 core_rev; - u8 vta_regs[3]; - u8 duplex_reg; - u8 jumbo_pm_reg; - u8 jumbo_size_reg; - int reset_gpio; - - /* used ports mask */ - u16 enabled_ports; - - /* connect specific data */ - u8 current_page; - struct device *dev; - void *priv; - - /* run time configuration */ - unsigned enable_vlan:1; - unsigned enable_jumbo:1; - unsigned allow_vid_4095:1; - - struct b53_port *ports; - struct b53_vlan *vlans; - - char *buf; -}; - -#define b53_for_each_port(dev, i) \ - for (i = 0; i < B53_N_PORTS; i++) \ - if (dev->enabled_ports & BIT(i)) - - - -static inline int is5325(struct b53_device *dev) -{ - return dev->chip_id == BCM5325_DEVICE_ID; -} - -static inline int is5365(struct b53_device *dev) -{ -#ifdef CONFIG_BCM47XX - return dev->chip_id == BCM5365_DEVICE_ID; -#else - return 0; -#endif -} - -static inline int is5397_98(struct b53_device *dev) -{ - return dev->chip_id == BCM5397_DEVICE_ID || - dev->chip_id == BCM5398_DEVICE_ID; -} - -static inline int is539x(struct b53_device *dev) -{ - return dev->chip_id == BCM5395_DEVICE_ID || - dev->chip_id == BCM5397_DEVICE_ID || - dev->chip_id == BCM5398_DEVICE_ID; -} - -static inline int is531x5(struct b53_device *dev) -{ - return dev->chip_id == BCM53115_DEVICE_ID || - dev->chip_id == BCM53125_DEVICE_ID || - dev->chip_id == BCM53128_DEVICE_ID; -} - -static inline int is63xx(struct b53_device *dev) -{ -#ifdef CONFIG_BCM63XX - return dev->chip_id == BCM63XX_DEVICE_ID; -#else - return 0; -#endif -} - -static inline int is5301x(struct b53_device *dev) -{ - return dev->chip_id == BCM53010_DEVICE_ID || - dev->chip_id == BCM53011_DEVICE_ID || - dev->chip_id == BCM53012_DEVICE_ID || - dev->chip_id == BCM53018_DEVICE_ID || - dev->chip_id == BCM53019_DEVICE_ID; -} - -#define B53_CPU_PORT_25 5 -#define B53_CPU_PORT 8 - -static inline int is_cpu_port(struct b53_device *dev, int port) -{ - return dev->sw_dev.cpu_port == port; -} - -static inline int is_imp_port(struct b53_device *dev, int port) -{ - if (is5325(dev) || is5365(dev)) - return port == B53_CPU_PORT_25; - else - return port == B53_CPU_PORT; -} - -static inline struct b53_device *sw_to_b53(struct switch_dev *sw) -{ - return container_of(sw, struct b53_device, sw_dev); -} - -struct b53_device *b53_switch_alloc(struct device *base, struct b53_io_ops *ops, - void *priv); - -int b53_switch_detect(struct b53_device *dev); - -int b53_switch_register(struct b53_device *dev); - -static inline void b53_switch_remove(struct b53_device *dev) -{ - unregister_switch(&dev->sw_dev); -} - -static inline int b53_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read8(dev, page, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read16(dev, page, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read32(dev, page, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read48(dev, page, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->read64(dev, page, reg, val); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write8(dev, page, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_write16(struct b53_device *dev, u8 page, u8 reg, - u16 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write16(dev, page, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_write32(struct b53_device *dev, u8 page, u8 reg, - u32 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write32(dev, page, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_write48(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write48(dev, page, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -static inline int b53_write64(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - int ret; - - mutex_lock(&dev->reg_mutex); - ret = dev->ops->write64(dev, page, reg, value); - mutex_unlock(&dev->reg_mutex); - - return ret; -} - -#ifdef CONFIG_BCM47XX -#include -#endif - -#include -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) -#include -#endif -static inline int b53_switch_get_reset_gpio(struct b53_device *dev) -{ -#ifdef CONFIG_BCM47XX - enum bcm47xx_board board = bcm47xx_board_get(); - - switch (board) { - case BCM47XX_BOARD_LINKSYS_WRT300NV11: - case BCM47XX_BOARD_LINKSYS_WRT310NV1: - return 8; - default: - break; - } -#endif - -#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)) - return bcm47xx_nvram_gpio_pin("robo_reset"); -#else - return -ENOENT; -#endif -} - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h b/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h deleted file mode 100644 index f0bf6744a7..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h +++ /dev/null @@ -1,348 +0,0 @@ -/* - * B53 register definitions - * - * Copyright (C) 2004 Broadcom Corporation - * Copyright (C) 2011-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __B53_REGS_H -#define __B53_REGS_H - -/* Management Port (SMP) Page offsets */ -#define B53_CTRL_PAGE 0x00 /* Control */ -#define B53_STAT_PAGE 0x01 /* Status */ -#define B53_MGMT_PAGE 0x02 /* Management Mode */ -#define B53_MIB_AC_PAGE 0x03 /* MIB Autocast */ -#define B53_ARLCTRL_PAGE 0x04 /* ARL Control */ -#define B53_ARLIO_PAGE 0x05 /* ARL Access */ -#define B53_FRAMEBUF_PAGE 0x06 /* Management frame access */ -#define B53_MEM_ACCESS_PAGE 0x08 /* Memory access */ - -/* PHY Registers */ -#define B53_PORT_MII_PAGE(i) (0x10 + (i)) /* Port i MII Registers */ -#define B53_IM_PORT_PAGE 0x18 /* Inverse MII Port (to EMAC) */ -#define B53_ALL_PORT_PAGE 0x19 /* All ports MII (broadcast) */ - -/* MIB registers */ -#define B53_MIB_PAGE(i) (0x20 + (i)) - -/* Quality of Service (QoS) Registers */ -#define B53_QOS_PAGE 0x30 - -/* Port VLAN Page */ -#define B53_PVLAN_PAGE 0x31 - -/* VLAN Registers */ -#define B53_VLAN_PAGE 0x34 - -/* Jumbo Frame Registers */ -#define B53_JUMBO_PAGE 0x40 - -/* CFP Configuration Registers Page */ -#define B53_CFP_PAGE 0xa1 - -/************************************************************************* - * Control Page registers - *************************************************************************/ - -/* Port Control Register (8 bit) */ -#define B53_PORT_CTRL(i) (0x00 + (i)) -#define PORT_CTRL_RX_DISABLE BIT(0) -#define PORT_CTRL_TX_DISABLE BIT(1) -#define PORT_CTRL_RX_BCST_EN BIT(2) /* Broadcast RX (P8 only) */ -#define PORT_CTRL_RX_MCST_EN BIT(3) /* Multicast RX (P8 only) */ -#define PORT_CTRL_RX_UCST_EN BIT(4) /* Unicast RX (P8 only) */ -#define PORT_CTRL_STP_STATE_S 5 -#define PORT_CTRL_STP_STATE_MASK (0x7 << PORT_CTRL_STP_STATE_S) - -/* SMP Control Register (8 bit) */ -#define B53_SMP_CTRL 0x0a - -/* Switch Mode Control Register (8 bit) */ -#define B53_SWITCH_MODE 0x0b -#define SM_SW_FWD_MODE BIT(0) /* 1 = Managed Mode */ -#define SM_SW_FWD_EN BIT(1) /* Forwarding Enable */ - -/* IMP Port state override register (8 bit) */ -#define B53_PORT_OVERRIDE_CTRL 0x0e -#define PORT_OVERRIDE_LINK BIT(0) -#define PORT_OVERRIDE_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ -#define PORT_OVERRIDE_SPEED_S 2 -#define PORT_OVERRIDE_SPEED_10M (0 << PORT_OVERRIDE_SPEED_S) -#define PORT_OVERRIDE_SPEED_100M (1 << PORT_OVERRIDE_SPEED_S) -#define PORT_OVERRIDE_SPEED_1000M (2 << PORT_OVERRIDE_SPEED_S) -#define PORT_OVERRIDE_RV_MII_25 BIT(4) /* BCM5325 only */ -#define PORT_OVERRIDE_RX_FLOW BIT(4) -#define PORT_OVERRIDE_TX_FLOW BIT(5) -#define PORT_OVERRIDE_SPEED_2000M BIT(6) /* BCM5301X only, requires setting 1000M */ -#define PORT_OVERRIDE_EN BIT(7) /* Use the register contents */ - -/* Power-down mode control */ -#define B53_PD_MODE_CTRL_25 0x0f - -/* IP Multicast control (8 bit) */ -#define B53_IP_MULTICAST_CTRL 0x21 -#define B53_IPMC_FWD_EN BIT(1) -#define B53_UC_FWD_EN BIT(6) -#define B53_MC_FWD_EN BIT(7) - -/* (16 bit) */ -#define B53_UC_FLOOD_MASK 0x32 -#define B53_MC_FLOOD_MASK 0x34 -#define B53_IPMC_FLOOD_MASK 0x36 - -/* - * Override Ports 0-7 State on devices with xMII interfaces (8 bit) - * - * For port 8 still use B53_PORT_OVERRIDE_CTRL - * Please note that not all ports are available on every hardware, e.g. BCM5301X - * don't include overriding port 6, BCM63xx also have some limitations. - */ -#define B53_GMII_PORT_OVERRIDE_CTRL(i) (0x58 + (i)) -#define GMII_PO_LINK BIT(0) -#define GMII_PO_FULL_DUPLEX BIT(1) /* 0 = Half Duplex */ -#define GMII_PO_SPEED_S 2 -#define GMII_PO_SPEED_10M (0 << GMII_PO_SPEED_S) -#define GMII_PO_SPEED_100M (1 << GMII_PO_SPEED_S) -#define GMII_PO_SPEED_1000M (2 << GMII_PO_SPEED_S) -#define GMII_PO_RX_FLOW BIT(4) -#define GMII_PO_TX_FLOW BIT(5) -#define GMII_PO_EN BIT(6) /* Use the register contents */ -#define GMII_PO_SPEED_2000M BIT(7) /* BCM5301X only, requires setting 1000M */ - -/* Software reset register (8 bit) */ -#define B53_SOFTRESET 0x79 - -/* Fast Aging Control register (8 bit) */ -#define B53_FAST_AGE_CTRL 0x88 -#define FAST_AGE_STATIC BIT(0) -#define FAST_AGE_DYNAMIC BIT(1) -#define FAST_AGE_PORT BIT(2) -#define FAST_AGE_VLAN BIT(3) -#define FAST_AGE_STP BIT(4) -#define FAST_AGE_MC BIT(5) -#define FAST_AGE_DONE BIT(7) - -/************************************************************************* - * Status Page registers - *************************************************************************/ - -/* Link Status Summary Register (16bit) */ -#define B53_LINK_STAT 0x00 - -/* Link Status Change Register (16 bit) */ -#define B53_LINK_STAT_CHANGE 0x02 - -/* Port Speed Summary Register (16 bit for FE, 32 bit for GE) */ -#define B53_SPEED_STAT 0x04 -#define SPEED_PORT_FE(reg, port) (((reg) >> (port)) & 1) -#define SPEED_PORT_GE(reg, port) (((reg) >> 2 * (port)) & 3) -#define SPEED_STAT_10M 0 -#define SPEED_STAT_100M 1 -#define SPEED_STAT_1000M 2 - -/* Duplex Status Summary (16 bit) */ -#define B53_DUPLEX_STAT_FE 0x06 -#define B53_DUPLEX_STAT_GE 0x08 -#define B53_DUPLEX_STAT_63XX 0x0c - -/* Revision ID register for BCM5325 */ -#define B53_REV_ID_25 0x50 - -/* Strap Value (48 bit) */ -#define B53_STRAP_VALUE 0x70 -#define SV_GMII_CTRL_115 BIT(27) - -/************************************************************************* - * Management Mode Page Registers - *************************************************************************/ - -/* Global Management Config Register (8 bit) */ -#define B53_GLOBAL_CONFIG 0x00 -#define GC_RESET_MIB 0x01 -#define GC_RX_BPDU_EN 0x02 -#define GC_MIB_AC_HDR_EN 0x10 -#define GC_MIB_AC_EN 0x20 -#define GC_FRM_MGMT_PORT_M 0xC0 -#define GC_FRM_MGMT_PORT_04 0x00 -#define GC_FRM_MGMT_PORT_MII 0x80 - -/* Broadcom Header control register (8 bit) */ -#define B53_BRCM_HDR 0x03 -#define BRCM_HDR_P8_EN BIT(0) /* Enable tagging on port 8 */ -#define BRCM_HDR_P5_EN BIT(1) /* Enable tagging on port 5 */ - -/* Device ID register (8 or 32 bit) */ -#define B53_DEVICE_ID 0x30 - -/* Revision ID register (8 bit) */ -#define B53_REV_ID 0x40 - -/************************************************************************* - * ARL Access Page Registers - *************************************************************************/ - -/* VLAN Table Access Register (8 bit) */ -#define B53_VT_ACCESS 0x80 -#define B53_VT_ACCESS_9798 0x60 /* for BCM5397/BCM5398 */ -#define B53_VT_ACCESS_63XX 0x60 /* for BCM6328/62/68 */ -#define VTA_CMD_WRITE 0 -#define VTA_CMD_READ 1 -#define VTA_CMD_CLEAR 2 -#define VTA_START_CMD BIT(7) - -/* VLAN Table Index Register (16 bit) */ -#define B53_VT_INDEX 0x81 -#define B53_VT_INDEX_9798 0x61 -#define B53_VT_INDEX_63XX 0x62 - -/* VLAN Table Entry Register (32 bit) */ -#define B53_VT_ENTRY 0x83 -#define B53_VT_ENTRY_9798 0x63 -#define B53_VT_ENTRY_63XX 0x64 -#define VTE_MEMBERS 0x1ff -#define VTE_UNTAG_S 9 -#define VTE_UNTAG (0x1ff << 9) - -/************************************************************************* - * Port VLAN Registers - *************************************************************************/ - -/* Port VLAN mask (16 bit) IMP port is always 8, also on 5325 & co */ -#define B53_PVLAN_PORT_MASK(i) ((i) * 2) - -/************************************************************************* - * 802.1Q Page Registers - *************************************************************************/ - -/* Global QoS Control (8 bit) */ -#define B53_QOS_GLOBAL_CTL 0x00 - -/* Enable 802.1Q for individual Ports (16 bit) */ -#define B53_802_1P_EN 0x04 - -/************************************************************************* - * VLAN Page Registers - *************************************************************************/ - -/* VLAN Control 0 (8 bit) */ -#define B53_VLAN_CTRL0 0x00 -#define VC0_8021PF_CTRL_MASK 0x3 -#define VC0_8021PF_CTRL_NONE 0x0 -#define VC0_8021PF_CTRL_CHANGE_PRI 0x1 -#define VC0_8021PF_CTRL_CHANGE_VID 0x2 -#define VC0_8021PF_CTRL_CHANGE_BOTH 0x3 -#define VC0_8021QF_CTRL_MASK 0xc -#define VC0_8021QF_CTRL_CHANGE_PRI 0x1 -#define VC0_8021QF_CTRL_CHANGE_VID 0x2 -#define VC0_8021QF_CTRL_CHANGE_BOTH 0x3 -#define VC0_RESERVED_1 BIT(1) -#define VC0_DROP_VID_MISS BIT(4) -#define VC0_VID_HASH_VID BIT(5) -#define VC0_VID_CHK_EN BIT(6) /* Use VID,DA or VID,SA */ -#define VC0_VLAN_EN BIT(7) /* 802.1Q VLAN Enabled */ - -/* VLAN Control 1 (8 bit) */ -#define B53_VLAN_CTRL1 0x01 -#define VC1_RX_MCST_TAG_EN BIT(1) -#define VC1_RX_MCST_FWD_EN BIT(2) -#define VC1_RX_MCST_UNTAG_EN BIT(3) - -/* VLAN Control 2 (8 bit) */ -#define B53_VLAN_CTRL2 0x02 - -/* VLAN Control 3 (8 bit when BCM5325, 16 bit else) */ -#define B53_VLAN_CTRL3 0x03 -#define B53_VLAN_CTRL3_63XX 0x04 -#define VC3_MAXSIZE_1532 BIT(6) /* 5325 only */ -#define VC3_HIGH_8BIT_EN BIT(7) /* 5325 only */ - -/* VLAN Control 4 (8 bit) */ -#define B53_VLAN_CTRL4 0x05 -#define B53_VLAN_CTRL4_25 0x04 -#define B53_VLAN_CTRL4_63XX 0x06 -#define VC4_ING_VID_CHECK_S 6 -#define VC4_ING_VID_CHECK_MASK (0x3 << VC4_ING_VID_CHECK_S) -#define VC4_ING_VID_VIO_FWD 0 /* forward, but do not learn */ -#define VC4_ING_VID_VIO_DROP 1 /* drop VID violations */ -#define VC4_NO_ING_VID_CHK 2 /* do not check */ -#define VC4_ING_VID_VIO_TO_IMP 3 /* redirect to MII port */ - -/* VLAN Control 5 (8 bit) */ -#define B53_VLAN_CTRL5 0x06 -#define B53_VLAN_CTRL5_25 0x05 -#define B53_VLAN_CTRL5_63XX 0x07 -#define VC5_VID_FFF_EN BIT(2) -#define VC5_DROP_VTABLE_MISS BIT(3) - -/* VLAN Control 6 (8 bit) */ -#define B53_VLAN_CTRL6 0x07 -#define B53_VLAN_CTRL6_63XX 0x08 - -/* VLAN Table Access Register (16 bit) */ -#define B53_VLAN_TABLE_ACCESS_25 0x06 /* BCM5325E/5350 */ -#define B53_VLAN_TABLE_ACCESS_65 0x08 /* BCM5365 */ -#define VTA_VID_LOW_MASK_25 0xf -#define VTA_VID_LOW_MASK_65 0xff -#define VTA_VID_HIGH_S_25 4 -#define VTA_VID_HIGH_S_65 8 -#define VTA_VID_HIGH_MASK_25 (0xff << VTA_VID_HIGH_S_25E) -#define VTA_VID_HIGH_MASK_65 (0xf << VTA_VID_HIGH_S_65) -#define VTA_RW_STATE BIT(12) -#define VTA_RW_STATE_RD 0 -#define VTA_RW_STATE_WR BIT(12) -#define VTA_RW_OP_EN BIT(13) - -/* VLAN Read/Write Registers for (16/32 bit) */ -#define B53_VLAN_WRITE_25 0x08 -#define B53_VLAN_WRITE_65 0x0a -#define B53_VLAN_READ 0x0c -#define VA_MEMBER_MASK 0x3f -#define VA_UNTAG_S_25 6 -#define VA_UNTAG_MASK_25 0x3f -#define VA_UNTAG_S_65 7 -#define VA_UNTAG_MASK_65 0x1f -#define VA_VID_HIGH_S 12 -#define VA_VID_HIGH_MASK (0xffff << VA_VID_HIGH_S) -#define VA_VALID_25 BIT(20) -#define VA_VALID_25_R4 BIT(24) -#define VA_VALID_65 BIT(14) - -/* VLAN Port Default Tag (16 bit) */ -#define B53_VLAN_PORT_DEF_TAG(i) (0x10 + 2 * (i)) - -/************************************************************************* - * Jumbo Frame Page Registers - *************************************************************************/ - -/* Jumbo Enable Port Mask (bit i == port i enabled) (32 bit) */ -#define B53_JUMBO_PORT_MASK 0x01 -#define B53_JUMBO_PORT_MASK_63XX 0x04 -#define JPM_10_100_JUMBO_EN BIT(24) /* GigE always enabled */ - -/* Good Frame Max Size without 802.1Q TAG (16 bit) */ -#define B53_JUMBO_MAX_SIZE 0x05 -#define B53_JUMBO_MAX_SIZE_63XX 0x08 -#define JMS_MIN_SIZE 1518 -#define JMS_MAX_SIZE 9724 - -/************************************************************************* - * CFP Configuration Page Registers - *************************************************************************/ - -/* CFP Control Register with ports map (8 bit) */ -#define B53_CFP_CTRL 0x00 - -#endif /* !__B53_REGS_H */ diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c b/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c deleted file mode 100644 index efc8f7ee89..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c +++ /dev/null @@ -1,344 +0,0 @@ -/* - * B53 register access through SPI - * - * Copyright (C) 2011-2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include - -#include -#include -#include -#include -#include - -#include "b53_priv.h" - -#define B53_SPI_DATA 0xf0 - -#define B53_SPI_STATUS 0xfe -#define B53_SPI_CMD_SPIF BIT(7) -#define B53_SPI_CMD_RACK BIT(5) - -#define B53_SPI_CMD_READ 0x00 -#define B53_SPI_CMD_WRITE 0x01 -#define B53_SPI_CMD_NORMAL 0x60 -#define B53_SPI_CMD_FAST 0x10 - -#define B53_SPI_PAGE_SELECT 0xff - -static inline int b53_spi_read_reg(struct spi_device *spi, u8 reg, u8 *val, - unsigned len) -{ - u8 txbuf[2]; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_READ; - txbuf[1] = reg; - - return spi_write_then_read(spi, txbuf, 2, val, len); -} - -static inline int b53_spi_clear_status(struct spi_device *spi) -{ - unsigned int i; - u8 rxbuf; - int ret; - - for (i = 0; i < 10; i++) { - ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); - if (ret) - return ret; - - if (!(rxbuf & B53_SPI_CMD_SPIF)) - break; - - mdelay(1); - } - - if (i == 10) - return -EIO; - - return 0; -} - -static inline int b53_spi_set_page(struct spi_device *spi, u8 page) -{ - u8 txbuf[3]; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = B53_SPI_PAGE_SELECT; - txbuf[2] = page; - - return spi_write(spi, txbuf, sizeof(txbuf)); -} - -static inline int b53_prepare_reg_access(struct spi_device *spi, u8 page) -{ - int ret = b53_spi_clear_status(spi); - - if (ret) - return ret; - - return b53_spi_set_page(spi, page); -} - -static int b53_spi_prepare_reg_read(struct spi_device *spi, u8 reg) -{ - u8 rxbuf; - int retry_count; - int ret; - - ret = b53_spi_read_reg(spi, reg, &rxbuf, 1); - if (ret) - return ret; - - for (retry_count = 0; retry_count < 10; retry_count++) { - ret = b53_spi_read_reg(spi, B53_SPI_STATUS, &rxbuf, 1); - if (ret) - return ret; - - if (rxbuf & B53_SPI_CMD_RACK) - break; - - mdelay(1); - } - - if (retry_count == 10) - return -EIO; - - return 0; -} - -static int b53_spi_read(struct b53_device *dev, u8 page, u8 reg, u8 *data, - unsigned len) -{ - struct spi_device *spi = dev->priv; - int ret; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - ret = b53_spi_prepare_reg_read(spi, reg); - if (ret) - return ret; - - return b53_spi_read_reg(spi, B53_SPI_DATA, data, len); -} - -static int b53_spi_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) -{ - return b53_spi_read(dev, page, reg, val, 1); -} - -static int b53_spi_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) -{ - int ret = b53_spi_read(dev, page, reg, (u8 *)val, 2); - - if (!ret) - *val = le16_to_cpu(*val); - - return ret; -} - -static int b53_spi_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) -{ - int ret = b53_spi_read(dev, page, reg, (u8 *)val, 4); - - if (!ret) - *val = le32_to_cpu(*val); - - return ret; -} - -static int b53_spi_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - int ret; - - *val = 0; - ret = b53_spi_read(dev, page, reg, (u8 *)val, 6); - if (!ret) - *val = le64_to_cpu(*val); - - return ret; -} - -static int b53_spi_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - int ret = b53_spi_read(dev, page, reg, (u8 *)val, 8); - - if (!ret) - *val = le64_to_cpu(*val); - - return ret; -} - -static int b53_spi_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) -{ - struct spi_device *spi = dev->priv; - int ret; - u8 txbuf[3]; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = reg; - txbuf[2] = value; - - return spi_write(spi, txbuf, sizeof(txbuf)); -} - -static int b53_spi_write16(struct b53_device *dev, u8 page, u8 reg, u16 value) -{ - struct spi_device *spi = dev->priv; - int ret; - u8 txbuf[4]; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = reg; - put_unaligned_le16(value, &txbuf[2]); - - return spi_write(spi, txbuf, sizeof(txbuf)); -} - -static int b53_spi_write32(struct b53_device *dev, u8 page, u8 reg, u32 value) -{ - struct spi_device *spi = dev->priv; - int ret; - u8 txbuf[6]; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = reg; - put_unaligned_le32(value, &txbuf[2]); - - return spi_write(spi, txbuf, sizeof(txbuf)); -} - -static int b53_spi_write48(struct b53_device *dev, u8 page, u8 reg, u64 value) -{ - struct spi_device *spi = dev->priv; - int ret; - u8 txbuf[10]; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = reg; - put_unaligned_le64(value, &txbuf[2]); - - return spi_write(spi, txbuf, sizeof(txbuf) - 2); -} - -static int b53_spi_write64(struct b53_device *dev, u8 page, u8 reg, u64 value) -{ - struct spi_device *spi = dev->priv; - int ret; - u8 txbuf[10]; - - ret = b53_prepare_reg_access(spi, page); - if (ret) - return ret; - - txbuf[0] = B53_SPI_CMD_NORMAL | B53_SPI_CMD_WRITE; - txbuf[1] = reg; - put_unaligned_le64(value, &txbuf[2]); - - return spi_write(spi, txbuf, sizeof(txbuf)); -} - -static struct b53_io_ops b53_spi_ops = { - .read8 = b53_spi_read8, - .read16 = b53_spi_read16, - .read32 = b53_spi_read32, - .read48 = b53_spi_read48, - .read64 = b53_spi_read64, - .write8 = b53_spi_write8, - .write16 = b53_spi_write16, - .write32 = b53_spi_write32, - .write48 = b53_spi_write48, - .write64 = b53_spi_write64, -}; - -static int b53_spi_probe(struct spi_device *spi) -{ - struct b53_device *dev; - int ret; - - dev = b53_switch_alloc(&spi->dev, &b53_spi_ops, spi); - if (!dev) - return -ENOMEM; - - if (spi->dev.platform_data) - dev->pdata = spi->dev.platform_data; - - ret = b53_switch_register(dev); - if (ret) - return ret; - - spi_set_drvdata(spi, dev); - - return 0; -} - -static int b53_spi_remove(struct spi_device *spi) -{ - struct b53_device *dev = spi_get_drvdata(spi); - - if (dev) - b53_switch_remove(dev); - - return 0; -} - -static const struct of_device_id b53_of_match[] = { - { .compatible = "brcm,bcm5325" }, - { .compatible = "brcm,bcm53115" }, - { .compatible = "brcm,bcm53125" }, - { .compatible = "brcm,bcm53128" }, - { .compatible = "brcm,bcm5365" }, - { .compatible = "brcm,bcm5395" }, - { .compatible = "brcm,bcm5397" }, - { .compatible = "brcm,bcm5398" }, - { /* sentinel */ }, -}; - -static struct spi_driver b53_spi_driver = { - .driver = { - .name = "b53-switch", - .bus = &spi_bus_type, - .owner = THIS_MODULE, - .of_match_table = b53_of_match, - }, - .probe = b53_spi_probe, - .remove = b53_spi_remove, -}; - -module_spi_driver(b53_spi_driver); - -MODULE_AUTHOR("Jonas Gorski "); -MODULE_DESCRIPTION("B53 SPI access driver"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c b/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c deleted file mode 100644 index 012daa3a51..0000000000 --- a/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * B53 register access through Switch Register Access Bridge Registers - * - * Copyright (C) 2013 Hauke Mehrtens - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include - -#include "b53_priv.h" - -/* command and status register of the SRAB */ -#define B53_SRAB_CMDSTAT 0x2c -#define B53_SRAB_CMDSTAT_RST BIT(2) -#define B53_SRAB_CMDSTAT_WRITE BIT(1) -#define B53_SRAB_CMDSTAT_GORDYN BIT(0) -#define B53_SRAB_CMDSTAT_PAGE 24 -#define B53_SRAB_CMDSTAT_REG 16 - -/* high order word of write data to switch registe */ -#define B53_SRAB_WD_H 0x30 - -/* low order word of write data to switch registe */ -#define B53_SRAB_WD_L 0x34 - -/* high order word of read data from switch register */ -#define B53_SRAB_RD_H 0x38 - -/* low order word of read data from switch register */ -#define B53_SRAB_RD_L 0x3c - -/* command and status register of the SRAB */ -#define B53_SRAB_CTRLS 0x40 -#define B53_SRAB_CTRLS_RCAREQ BIT(3) -#define B53_SRAB_CTRLS_RCAGNT BIT(4) -#define B53_SRAB_CTRLS_SW_INIT_DONE BIT(6) - -/* the register captures interrupt pulses from the switch */ -#define B53_SRAB_INTR 0x44 - -static int b53_srab_request_grant(struct b53_device *dev) -{ - u8 __iomem *regs = dev->priv; - u32 ctrls; - int i; - - ctrls = readl(regs + B53_SRAB_CTRLS); - ctrls |= B53_SRAB_CTRLS_RCAREQ; - writel(ctrls, regs + B53_SRAB_CTRLS); - - for (i = 0; i < 20; i++) { - ctrls = readl(regs + B53_SRAB_CTRLS); - if (ctrls & B53_SRAB_CTRLS_RCAGNT) - break; - usleep_range(10, 100); - } - if (WARN_ON(i == 5)) - return -EIO; - - return 0; -} - -static void b53_srab_release_grant(struct b53_device *dev) -{ - u8 __iomem *regs = dev->priv; - u32 ctrls; - - ctrls = readl(regs + B53_SRAB_CTRLS); - ctrls &= ~B53_SRAB_CTRLS_RCAREQ; - writel(ctrls, regs + B53_SRAB_CTRLS); -} - -static int b53_srab_op(struct b53_device *dev, u8 page, u8 reg, u32 op) -{ - int i; - u32 cmdstat; - u8 __iomem *regs = dev->priv; - - /* set register address */ - cmdstat = (page << B53_SRAB_CMDSTAT_PAGE) | - (reg << B53_SRAB_CMDSTAT_REG) | - B53_SRAB_CMDSTAT_GORDYN | - op; - writel(cmdstat, regs + B53_SRAB_CMDSTAT); - - /* check if operation completed */ - for (i = 0; i < 5; ++i) { - cmdstat = readl(regs + B53_SRAB_CMDSTAT); - if (!(cmdstat & B53_SRAB_CMDSTAT_GORDYN)) - break; - usleep_range(10, 100); - } - - if (WARN_ON(i == 5)) - return -EIO; - - return 0; -} - -static int b53_srab_read8(struct b53_device *dev, u8 page, u8 reg, u8 *val) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - ret = b53_srab_op(dev, page, reg, 0); - if (ret) - goto err; - - *val = readl(regs + B53_SRAB_RD_L) & 0xff; - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_read16(struct b53_device *dev, u8 page, u8 reg, u16 *val) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - ret = b53_srab_op(dev, page, reg, 0); - if (ret) - goto err; - - *val = readl(regs + B53_SRAB_RD_L) & 0xffff; - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_read32(struct b53_device *dev, u8 page, u8 reg, u32 *val) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - ret = b53_srab_op(dev, page, reg, 0); - if (ret) - goto err; - - *val = readl(regs + B53_SRAB_RD_L); - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_read48(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - ret = b53_srab_op(dev, page, reg, 0); - if (ret) - goto err; - - *val = readl(regs + B53_SRAB_RD_L); - *val += ((u64)readl(regs + B53_SRAB_RD_H) & 0xffff) << 32; - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_read64(struct b53_device *dev, u8 page, u8 reg, u64 *val) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - ret = b53_srab_op(dev, page, reg, 0); - if (ret) - goto err; - - *val = readl(regs + B53_SRAB_RD_L); - *val += (u64)readl(regs + B53_SRAB_RD_H) << 32; - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_write8(struct b53_device *dev, u8 page, u8 reg, u8 value) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - writel(value, regs + B53_SRAB_WD_L); - - ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_write16(struct b53_device *dev, u8 page, u8 reg, - u16 value) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - writel(value, regs + B53_SRAB_WD_L); - - ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static int b53_srab_write32(struct b53_device *dev, u8 page, u8 reg, - u32 value) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - writel(value, regs + B53_SRAB_WD_L); - - ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); - -err: - b53_srab_release_grant(dev); - - return ret; - -} - -static int b53_srab_write48(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - writel((u32)value, regs + B53_SRAB_WD_L); - writel((u16)(value >> 32), regs + B53_SRAB_WD_H); - - ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); - -err: - b53_srab_release_grant(dev); - - return ret; - -} - -static int b53_srab_write64(struct b53_device *dev, u8 page, u8 reg, - u64 value) -{ - u8 __iomem *regs = dev->priv; - int ret = 0; - - ret = b53_srab_request_grant(dev); - if (ret) - goto err; - - writel((u32)value, regs + B53_SRAB_WD_L); - writel((u32)(value >> 32), regs + B53_SRAB_WD_H); - - ret = b53_srab_op(dev, page, reg, B53_SRAB_CMDSTAT_WRITE); - -err: - b53_srab_release_grant(dev); - - return ret; -} - -static struct b53_io_ops b53_srab_ops = { - .read8 = b53_srab_read8, - .read16 = b53_srab_read16, - .read32 = b53_srab_read32, - .read48 = b53_srab_read48, - .read64 = b53_srab_read64, - .write8 = b53_srab_write8, - .write16 = b53_srab_write16, - .write32 = b53_srab_write32, - .write48 = b53_srab_write48, - .write64 = b53_srab_write64, -}; - -static int b53_srab_probe(struct platform_device *pdev) -{ - struct b53_platform_data *pdata = pdev->dev.platform_data; - struct b53_device *dev; - - if (!pdata) - return -EINVAL; - - dev = b53_switch_alloc(&pdev->dev, &b53_srab_ops, pdata->regs); - if (!dev) - return -ENOMEM; - - if (pdata) - dev->pdata = pdata; - - platform_set_drvdata(pdev, dev); - - return b53_switch_register(dev); -} - -static int b53_srab_remove(struct platform_device *pdev) -{ - struct b53_device *dev = platform_get_drvdata(pdev); - - if (dev) - b53_switch_remove(dev); - - return 0; -} - -static struct platform_driver b53_srab_driver = { - .probe = b53_srab_probe, - .remove = b53_srab_remove, - .driver = { - .name = "b53-srab-switch", - }, -}; - -module_platform_driver(b53_srab_driver); -MODULE_AUTHOR("Hauke Mehrtens "); -MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver"); -MODULE_LICENSE("Dual BSD/GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/ip17xx.c b/target/linux/generic/files/drivers/net/phy/ip17xx.c deleted file mode 100644 index 85a9617a5d..0000000000 --- a/target/linux/generic/files/drivers/net/phy/ip17xx.c +++ /dev/null @@ -1,1377 +0,0 @@ -/* - * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family - * - * Copyright (C) 2008 Patrick Horn - * Copyright (C) 2008, 2010 Martin Mares - * Copyright (C) 2009 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MAX_VLANS 16 -#define MAX_PORTS 9 -#undef DUMP_MII_IO - -typedef struct ip17xx_reg { - u16 p; // phy - u16 m; // mii -} reg; -typedef char bitnum; - -#define NOTSUPPORTED {-1,-1} - -#define REG_SUPP(x) (((x).m != ((u16)-1)) && ((x).p != (u16)-1)) - -struct ip17xx_state; - -/*********** CONSTANTS ***********/ -struct register_mappings { - char *NAME; - u16 MODEL_NO; // Compare to bits 4-9 of MII register 0,3. - bitnum NUM_PORTS; - bitnum CPU_PORT; - -/* The default VLAN for each port. - Default: 0x0001 for Ports 0,1,2,3 - 0x0002 for Ports 4,5 */ - reg VLAN_DEFAULT_TAG_REG[MAX_PORTS]; - -/* These ports are tagged. - Default: 0x00 */ - reg ADD_TAG_REG; - reg REMOVE_TAG_REG; - bitnum ADD_TAG_BIT[MAX_PORTS]; -/* These ports are untagged. - Default: 0x00 (i.e. do not alter any VLAN tags...) - Maybe set to 0 if user disables VLANs. */ - bitnum REMOVE_TAG_BIT[MAX_PORTS]; - -/* Port M and Port N are on the same VLAN. - Default: All ports on all VLANs. */ -// Use register {29, 19+N/2} - reg VLAN_LOOKUP_REG; -// Port 5 uses register {30, 18} but same as odd bits. - reg VLAN_LOOKUP_REG_5; // in a different register on IP175C. - bitnum VLAN_LOOKUP_EVEN_BIT[MAX_PORTS]; - bitnum VLAN_LOOKUP_ODD_BIT[MAX_PORTS]; - -/* This VLAN corresponds to which ports. - Default: 0x2f,0x30,0x3f,0x3f... */ - reg TAG_VLAN_MASK_REG; - bitnum TAG_VLAN_MASK_EVEN_BIT[MAX_PORTS]; - bitnum TAG_VLAN_MASK_ODD_BIT[MAX_PORTS]; - - int RESET_VAL; - reg RESET_REG; - - reg MODE_REG; - int MODE_VAL; - -/* General flags */ - reg ROUTER_CONTROL_REG; - reg VLAN_CONTROL_REG; - bitnum TAG_VLAN_BIT; - bitnum ROUTER_EN_BIT; - bitnum NUMLAN_GROUPS_MAX; - bitnum NUMLAN_GROUPS_BIT; - - reg MII_REGISTER_EN; - bitnum MII_REGISTER_EN_BIT; - - // set to 1 for 178C, 0 for 175C. - bitnum SIMPLE_VLAN_REGISTERS; // 175C has two vlans per register but 178C has only one. - - // Pointers to functions which manipulate hardware state - int (*update_state)(struct ip17xx_state *state); - int (*set_vlan_mode)(struct ip17xx_state *state); - int (*reset)(struct ip17xx_state *state); -}; - -static int ip175c_update_state(struct ip17xx_state *state); -static int ip175c_set_vlan_mode(struct ip17xx_state *state); -static int ip175c_reset(struct ip17xx_state *state); - -static const struct register_mappings IP178C = { - .NAME = "IP178C", - .MODEL_NO = 0x18, - .VLAN_DEFAULT_TAG_REG = { - {30,3},{30,4},{30,5},{30,6},{30,7},{30,8}, - {30,9},{30,10},{30,11}, - }, - - .ADD_TAG_REG = {30,12}, - .ADD_TAG_BIT = {0,1,2,3,4,5,6,7,8}, - .REMOVE_TAG_REG = {30,13}, - .REMOVE_TAG_BIT = {4,5,6,7,8,9,10,11,12}, - - .SIMPLE_VLAN_REGISTERS = 1, - - .VLAN_LOOKUP_REG = {31,0},// +N - .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, // not used with SIMPLE_VLAN_REGISTERS - .VLAN_LOOKUP_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, - .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,5,6,7,8}, - - .TAG_VLAN_MASK_REG = {30,14}, // +N - .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,6,7,8}, - .TAG_VLAN_MASK_ODD_BIT = {0,1,2,3,4,5,6,7,8}, - - .RESET_VAL = 0x55AA, - .RESET_REG = {30,0}, - .MODE_VAL = 0, - .MODE_REG = NOTSUPPORTED, - - .ROUTER_CONTROL_REG = {30,30}, - .ROUTER_EN_BIT = 11, - .NUMLAN_GROUPS_MAX = 8, - .NUMLAN_GROUPS_BIT = 8, // {0-2} - - .VLAN_CONTROL_REG = {30,13}, - .TAG_VLAN_BIT = 3, - - .CPU_PORT = 8, - .NUM_PORTS = 9, - - .MII_REGISTER_EN = NOTSUPPORTED, - - .update_state = ip175c_update_state, - .set_vlan_mode = ip175c_set_vlan_mode, - .reset = ip175c_reset, -}; - -static const struct register_mappings IP175C = { - .NAME = "IP175C", - .MODEL_NO = 0x18, - .VLAN_DEFAULT_TAG_REG = { - {29,24},{29,25},{29,26},{29,27},{29,28},{29,30}, - NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED - }, - - .ADD_TAG_REG = {29,23}, - .REMOVE_TAG_REG = {29,23}, - .ADD_TAG_BIT = {11,12,13,14,15,1,-1,-1,-1}, - .REMOVE_TAG_BIT = {6,7,8,9,10,0,-1,-1,-1}, - - .SIMPLE_VLAN_REGISTERS = 0, - - .VLAN_LOOKUP_REG = {29,19},// +N/2 - .VLAN_LOOKUP_REG_5 = {30,18}, - .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,15,-1,-1,-1}, - .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,7,-1,-1,-1}, - - .TAG_VLAN_MASK_REG = {30,1}, // +N/2 - .TAG_VLAN_MASK_EVEN_BIT = {0,1,2,3,4,5,-1,-1,-1}, - .TAG_VLAN_MASK_ODD_BIT = {8,9,10,11,12,13,-1,-1,-1}, - - .RESET_VAL = 0x175C, - .RESET_REG = {30,0}, - .MODE_VAL = 0x175C, - .MODE_REG = {29,31}, - - .ROUTER_CONTROL_REG = {30,9}, - .ROUTER_EN_BIT = 3, - .NUMLAN_GROUPS_MAX = 8, - .NUMLAN_GROUPS_BIT = 0, // {0-2} - - .VLAN_CONTROL_REG = {30,9}, - .TAG_VLAN_BIT = 7, - - .NUM_PORTS = 6, - .CPU_PORT = 5, - - .MII_REGISTER_EN = NOTSUPPORTED, - - .update_state = ip175c_update_state, - .set_vlan_mode = ip175c_set_vlan_mode, - .reset = ip175c_reset, -}; - -static const struct register_mappings IP175A = { - .NAME = "IP175A", - .MODEL_NO = 0x05, - .VLAN_DEFAULT_TAG_REG = { - {0,24},{0,25},{0,26},{0,27},{0,28},NOTSUPPORTED, - NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED - }, - - .ADD_TAG_REG = {0,23}, - .REMOVE_TAG_REG = {0,23}, - .ADD_TAG_BIT = {11,12,13,14,15,-1,-1,-1,-1}, - .REMOVE_TAG_BIT = {6,7,8,9,10,-1,-1,-1,-1}, - - .SIMPLE_VLAN_REGISTERS = 0, - - // Only programmable via EEPROM - .VLAN_LOOKUP_REG = NOTSUPPORTED,// +N/2 - .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, - .VLAN_LOOKUP_EVEN_BIT = {8,9,10,11,12,-1,-1,-1,-1}, - .VLAN_LOOKUP_ODD_BIT = {0,1,2,3,4,-1,-1,-1,-1}, - - .TAG_VLAN_MASK_REG = NOTSUPPORTED, // +N/2, - .TAG_VLAN_MASK_EVEN_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, - .TAG_VLAN_MASK_ODD_BIT = {-1,-1,-1,-1,-1,-1,-1,-1,-1}, - - .RESET_VAL = -1, - .RESET_REG = NOTSUPPORTED, - .MODE_VAL = 0, - .MODE_REG = NOTSUPPORTED, - - .ROUTER_CONTROL_REG = NOTSUPPORTED, - .VLAN_CONTROL_REG = NOTSUPPORTED, - .TAG_VLAN_BIT = -1, - .ROUTER_EN_BIT = -1, - .NUMLAN_GROUPS_MAX = -1, - .NUMLAN_GROUPS_BIT = -1, // {0-2} - - .NUM_PORTS = 5, - .CPU_PORT = 4, - - .MII_REGISTER_EN = {0, 18}, - .MII_REGISTER_EN_BIT = 7, - - .update_state = ip175c_update_state, - .set_vlan_mode = ip175c_set_vlan_mode, - .reset = ip175c_reset, -}; - - -static int ip175d_update_state(struct ip17xx_state *state); -static int ip175d_set_vlan_mode(struct ip17xx_state *state); -static int ip175d_reset(struct ip17xx_state *state); - -static const struct register_mappings IP175D = { - .NAME = "IP175D", - .MODEL_NO = 0x18, - - // The IP175D has a completely different interface, so we leave most - // of the registers undefined and switch to different code paths. - - .VLAN_DEFAULT_TAG_REG = { - NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, - NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED,NOTSUPPORTED, - }, - - .ADD_TAG_REG = NOTSUPPORTED, - .REMOVE_TAG_REG = NOTSUPPORTED, - - .SIMPLE_VLAN_REGISTERS = 0, - - .VLAN_LOOKUP_REG = NOTSUPPORTED, - .VLAN_LOOKUP_REG_5 = NOTSUPPORTED, - .TAG_VLAN_MASK_REG = NOTSUPPORTED, - - .RESET_VAL = 0x175D, - .RESET_REG = {20,2}, - .MODE_REG = NOTSUPPORTED, - - .ROUTER_CONTROL_REG = NOTSUPPORTED, - .ROUTER_EN_BIT = -1, - .NUMLAN_GROUPS_BIT = -1, - - .VLAN_CONTROL_REG = NOTSUPPORTED, - .TAG_VLAN_BIT = -1, - - .NUM_PORTS = 6, - .CPU_PORT = 5, - - .MII_REGISTER_EN = NOTSUPPORTED, - - .update_state = ip175d_update_state, - .set_vlan_mode = ip175d_set_vlan_mode, - .reset = ip175d_reset, -}; - -struct ip17xx_state { - struct switch_dev dev; - struct mii_bus *mii_bus; - bool registered; - - int router_mode; // ROUTER_EN - int vlan_enabled; // TAG_VLAN_EN - struct port_state { - u16 pvid; - unsigned int shareports; - } ports[MAX_PORTS]; - unsigned int add_tag; - unsigned int remove_tag; - int num_vlans; - struct vlan_state { - unsigned int ports; - unsigned int tag; // VLAN tag (IP175D only) - } vlans[MAX_VLANS]; - const struct register_mappings *regs; - reg proc_mii; // phy/reg for the low level register access via swconfig - - char buf[80]; -}; - -#define get_state(_dev) container_of((_dev), struct ip17xx_state, dev) - -static int ip_phy_read(struct ip17xx_state *state, int port, int reg) -{ - int val = mdiobus_read(state->mii_bus, port, reg); - if (val < 0) - pr_warning("IP17xx: Unable to get MII register %d,%d: error %d\n", port, reg, -val); -#ifdef DUMP_MII_IO - else - pr_debug("IP17xx: Read MII(%d,%d) -> %04x\n", port, reg, val); -#endif - return val; -} - -static int ip_phy_write(struct ip17xx_state *state, int port, int reg, u16 val) -{ - int err; - -#ifdef DUMP_MII_IO - pr_debug("IP17xx: Write MII(%d,%d) <- %04x\n", port, reg, val); -#endif - err = mdiobus_write(state->mii_bus, port, reg, val); - if (err < 0) - pr_warning("IP17xx: Unable to write MII register %d,%d: error %d\n", port, reg, -err); - return err; -} - -static int ip_phy_write_masked(struct ip17xx_state *state, int port, int reg, unsigned int mask, unsigned int data) -{ - int val = ip_phy_read(state, port, reg); - if (val < 0) - return 0; - return ip_phy_write(state, port, reg, (val & ~mask) | data); -} - -static int getPhy(struct ip17xx_state *state, reg mii) -{ - if (!REG_SUPP(mii)) - return -EFAULT; - return ip_phy_read(state, mii.p, mii.m); -} - -static int setPhy(struct ip17xx_state *state, reg mii, u16 value) -{ - int err; - - if (!REG_SUPP(mii)) - return -EFAULT; - err = ip_phy_write(state, mii.p, mii.m, value); - if (err < 0) - return err; - mdelay(2); - getPhy(state, mii); - return 0; -} - - -/** - * These two macros are to simplify the mapping of logical bits to the bits in hardware. - * NOTE: these macros will return if there is an error! - */ - -#define GET_PORT_BITS(state, bits, addr, bit_lookup) \ - do { \ - int i, val = getPhy((state), (addr)); \ - if (val < 0) \ - return val; \ - (bits) = 0; \ - for (i = 0; i < MAX_PORTS; i++) { \ - if ((bit_lookup)[i] == -1) continue; \ - if (val & (1<<(bit_lookup)[i])) \ - (bits) |= (1<>i)<<(bit_lookup)[i]); \ - } \ - val = setPhy((state), (addr), val); \ - if (val < 0) \ - return val; \ - } while (0) - - -static int get_model(struct ip17xx_state *state) -{ - int id1, id2; - int oui_id, model_no, rev_no, chip_no; - - id1 = ip_phy_read(state, 0, 2); - id2 = ip_phy_read(state, 0, 3); - oui_id = (id1 << 6) | ((id2 >> 10) & 0x3f); - model_no = (id2 >> 4) & 0x3f; - rev_no = id2 & 0xf; - pr_debug("IP17xx: Identified oui=%06x model=%02x rev=%X\n", oui_id, model_no, rev_no); - - if (oui_id != 0x0090c3) // No other oui_id should have reached us anyway - return -ENODEV; - - if (model_no == IP175A.MODEL_NO) { - state->regs = &IP175A; - } else if (model_no == IP175C.MODEL_NO) { - /* - * Several models share the same model_no: - * 178C has more PHYs, so we try whether the device responds to a read from PHY5 - * 175D has a new chip ID register - * 175C has neither - */ - if (ip_phy_read(state, 5, 2) == 0x0243) { - state->regs = &IP178C; - } else { - chip_no = ip_phy_read(state, 20, 0); - pr_debug("IP17xx: Chip ID register reads %04x\n", chip_no); - if (chip_no == 0x175d) { - state->regs = &IP175D; - } else { - state->regs = &IP175C; - } - } - } else { - pr_warning("IP17xx: Found an unknown IC+ switch with model number %02x, revision %X.\n", model_no, rev_no); - return -EPERM; - } - return 0; -} - -/*** Low-level functions for the older models ***/ - -/** Only set vlan and router flags in the switch **/ -static int ip175c_set_flags(struct ip17xx_state *state) -{ - int val; - - if (!REG_SUPP(state->regs->ROUTER_CONTROL_REG)) { - return 0; - } - - val = getPhy(state, state->regs->ROUTER_CONTROL_REG); - if (val < 0) { - return val; - } - if (state->regs->ROUTER_EN_BIT >= 0) { - if (state->router_mode) { - val |= (1<regs->ROUTER_EN_BIT); - } else { - val &= (~(1<regs->ROUTER_EN_BIT)); - } - } - if (state->regs->TAG_VLAN_BIT >= 0) { - if (state->vlan_enabled) { - val |= (1<regs->TAG_VLAN_BIT); - } else { - val &= (~(1<regs->TAG_VLAN_BIT)); - } - } - if (state->regs->NUMLAN_GROUPS_BIT >= 0) { - val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<regs->NUMLAN_GROUPS_BIT)); - if (state->num_vlans > state->regs->NUMLAN_GROUPS_MAX) { - val |= state->regs->NUMLAN_GROUPS_MAX << state->regs->NUMLAN_GROUPS_BIT; - } else if (state->num_vlans >= 1) { - val |= (state->num_vlans-1) << state->regs->NUMLAN_GROUPS_BIT; - } - } - return setPhy(state, state->regs->ROUTER_CONTROL_REG, val); -} - -/** Set all VLAN and port state. Usually you should call "correct_vlan_state" first. **/ -static int ip175c_set_state(struct ip17xx_state *state) -{ - int j; - int i; - SET_PORT_BITS(state, state->add_tag, - state->regs->ADD_TAG_REG, state->regs->ADD_TAG_BIT); - SET_PORT_BITS(state, state->remove_tag, - state->regs->REMOVE_TAG_REG, state->regs->REMOVE_TAG_BIT); - - if (REG_SUPP(state->regs->VLAN_LOOKUP_REG)) { - for (j=0; jregs->NUM_PORTS; j++) { - reg addr; - const bitnum *bit_lookup = (j%2==0)? - state->regs->VLAN_LOOKUP_EVEN_BIT: - state->regs->VLAN_LOOKUP_ODD_BIT; - - addr = state->regs->VLAN_LOOKUP_REG; - if (state->regs->SIMPLE_VLAN_REGISTERS) { - addr.m += j; - } else { - switch (j) { - case 0: - case 1: - break; - case 2: - case 3: - addr.m+=1; - break; - case 4: - addr.m+=2; - break; - case 5: - addr = state->regs->VLAN_LOOKUP_REG_5; - break; - default: - addr.m = -1; // shouldn't get here, but... - break; - } - } - //printf("shareports for %d is %02X\n",j,state->ports[j].shareports); - if (REG_SUPP(addr)) { - SET_PORT_BITS(state, state->ports[j].shareports, addr, bit_lookup); - } - } - } - if (REG_SUPP(state->regs->TAG_VLAN_MASK_REG)) { - for (j=0; jregs->TAG_VLAN_MASK_REG; - const bitnum *bit_lookup = (j%2==0)? - state->regs->TAG_VLAN_MASK_EVEN_BIT: - state->regs->TAG_VLAN_MASK_ODD_BIT; - unsigned int vlan_mask; - if (state->regs->SIMPLE_VLAN_REGISTERS) { - addr.m += j; - } else { - addr.m += j/2; - } - vlan_mask = state->vlans[j].ports; - SET_PORT_BITS(state, vlan_mask, addr, bit_lookup); - } - } - - for (i=0; iregs->VLAN_DEFAULT_TAG_REG[i])) { - int err = setPhy(state, state->regs->VLAN_DEFAULT_TAG_REG[i], - state->ports[i].pvid); - if (err < 0) { - return err; - } - } - } - - return ip175c_set_flags(state); -} - -/** - * Uses only the VLAN port mask and the add tag mask to generate the other fields: - * which ports are part of the same VLAN, removing vlan tags, and VLAN tag ids. - */ -static void ip175c_correct_vlan_state(struct ip17xx_state *state) -{ - int i, j; - state->num_vlans = 0; - for (i=0; ivlans[i].ports != 0) { - state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere... - } - } - - for (i=0; iregs->NUM_PORTS; i++) { - unsigned int portmask = (1<vlan_enabled) { - // Share with everybody! - state->ports[i].shareports = (1<regs->NUM_PORTS)-1; - continue; - } - state->ports[i].shareports = portmask; - for (j=0; jvlans[j].ports & portmask) - state->ports[i].shareports |= state->vlans[j].ports; - } - } -} - -static int ip175c_update_state(struct ip17xx_state *state) -{ - ip175c_correct_vlan_state(state); - return ip175c_set_state(state); -} - -static int ip175c_set_vlan_mode(struct ip17xx_state *state) -{ - return ip175c_update_state(state); -} - -static int ip175c_reset(struct ip17xx_state *state) -{ - int err; - - if (REG_SUPP(state->regs->MODE_REG)) { - err = setPhy(state, state->regs->MODE_REG, state->regs->MODE_VAL); - if (err < 0) - return err; - err = getPhy(state, state->regs->MODE_REG); - if (err < 0) - return err; - } - - return ip175c_update_state(state); -} - -/*** Low-level functions for IP175D ***/ - -static int ip175d_update_state(struct ip17xx_state *state) -{ - unsigned int filter_mask = 0; - unsigned int ports[16], add[16], rem[16]; - int i, j; - int err = 0; - - for (i = 0; i < 16; i++) { - ports[i] = 0; - add[i] = 0; - rem[i] = 0; - if (!state->vlan_enabled) { - err |= ip_phy_write(state, 22, 14+i, i+1); // default tags - ports[i] = 0x3f; - continue; - } - if (!state->vlans[i].tag) { - // Reset the filter - err |= ip_phy_write(state, 22, 14+i, 0); // tag - continue; - } - filter_mask |= 1 << i; - err |= ip_phy_write(state, 22, 14+i, state->vlans[i].tag); - ports[i] = state->vlans[i].ports; - for (j = 0; j < 6; j++) { - if (ports[i] & (1 << j)) { - if (state->add_tag & (1 << j)) - add[i] |= 1 << j; - if (state->remove_tag & (1 << j)) - rem[i] |= 1 << j; - } - } - } - - // Port masks, tag adds and removals - for (i = 0; i < 8; i++) { - err |= ip_phy_write(state, 23, i, ports[2*i] | (ports[2*i+1] << 8)); - err |= ip_phy_write(state, 23, 8+i, add[2*i] | (add[2*i+1] << 8)); - err |= ip_phy_write(state, 23, 16+i, rem[2*i] | (rem[2*i+1] << 8)); - } - err |= ip_phy_write(state, 22, 10, filter_mask); - - // Default VLAN tag for each port - for (i = 0; i < 6; i++) - err |= ip_phy_write(state, 22, 4+i, state->vlans[state->ports[i].pvid].tag); - - return (err ? -EIO : 0); -} - -static int ip175d_set_vlan_mode(struct ip17xx_state *state) -{ - int i; - int err = 0; - - if (state->vlan_enabled) { - // VLAN classification rules: tag-based VLANs, use VID to classify, - // drop packets that cannot be classified. - err |= ip_phy_write_masked(state, 22, 0, 0x3fff, 0x003f); - - // Ingress rules: CFI=1 dropped, null VID is untagged, VID=1 passed, - // VID=0xfff discarded, admin both tagged and untagged, ingress - // filters enabled. - err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); - - // Egress rules: IGMP processing off, keep VLAN header off - err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); - } else { - // VLAN classification rules: everything off & clear table - err |= ip_phy_write_masked(state, 22, 0, 0xbfff, 0x8000); - - // Ingress and egress rules: set to defaults - err |= ip_phy_write_masked(state, 22, 1, 0x0fff, 0x0c3f); - err |= ip_phy_write_masked(state, 22, 2, 0x0fff, 0x0000); - } - - // Reset default VLAN for each port to 0 - for (i = 0; i < 6; i++) - state->ports[i].pvid = 0; - - err |= ip175d_update_state(state); - - return (err ? -EIO : 0); -} - -static int ip175d_reset(struct ip17xx_state *state) -{ - int err = 0; - - // Disable the special tagging mode - err |= ip_phy_write_masked(state, 21, 22, 0x0003, 0x0000); - - // Set 802.1q protocol type - err |= ip_phy_write(state, 22, 3, 0x8100); - - state->vlan_enabled = 0; - err |= ip175d_set_vlan_mode(state); - - return (err ? -EIO : 0); -} - -/*** High-level functions ***/ - -static int ip17xx_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - - val->value.i = state->vlan_enabled; - return 0; -} - -static void ip17xx_reset_vlan_config(struct ip17xx_state *state) -{ - int i; - - state->remove_tag = (state->vlan_enabled ? ((1<regs->NUM_PORTS)-1) : 0x0000); - state->add_tag = 0x0000; - for (i = 0; i < MAX_VLANS; i++) { - state->vlans[i].ports = 0x0000; - state->vlans[i].tag = (i ? i : 16); - } - for (i = 0; i < MAX_PORTS; i++) - state->ports[i].pvid = 0; -} - -static int ip17xx_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int enable; - - enable = val->value.i; - if (state->vlan_enabled == enable) { - // Do not change any state. - return 0; - } - state->vlan_enabled = enable; - - // Otherwise, if we are switching state, set fields to a known default. - ip17xx_reset_vlan_config(state); - - return state->regs->set_vlan_mode(state); -} - -static int ip17xx_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int b; - int ind; - unsigned int ports; - - if (val->port_vlan >= dev->vlans || val->port_vlan < 0) - return -EINVAL; - - ports = state->vlans[val->port_vlan].ports; - b = 0; - ind = 0; - while (b < MAX_PORTS) { - if (ports&1) { - int istagged = ((state->add_tag >> b) & 1); - val->value.ports[ind].id = b; - val->value.ports[ind].flags = (istagged << SWITCH_PORT_FLAG_TAGGED); - ind++; - } - b++; - ports >>= 1; - } - val->len = ind; - - return 0; -} - -static int ip17xx_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int i; - - if (val->port_vlan >= dev->vlans || val->port_vlan < 0) - return -EINVAL; - - state->vlans[val->port_vlan].ports = 0; - for (i = 0; i < val->len; i++) { - unsigned int bitmask = (1<value.ports[i].id); - state->vlans[val->port_vlan].ports |= bitmask; - if (val->value.ports[i].flags & (1<add_tag |= bitmask; - state->remove_tag &= (~bitmask); - } else { - state->add_tag &= (~bitmask); - state->remove_tag |= bitmask; - } - } - - return state->regs->update_state(state); -} - -static int ip17xx_apply(struct switch_dev *dev) -{ - struct ip17xx_state *state = get_state(dev); - - if (REG_SUPP(state->regs->MII_REGISTER_EN)) { - int val = getPhy(state, state->regs->MII_REGISTER_EN); - if (val < 0) { - return val; - } - val |= (1<regs->MII_REGISTER_EN_BIT); - return setPhy(state, state->regs->MII_REGISTER_EN, val); - } - return 0; -} - -static int ip17xx_reset(struct switch_dev *dev) -{ - struct ip17xx_state *state = get_state(dev); - int i, err; - - if (REG_SUPP(state->regs->RESET_REG)) { - err = setPhy(state, state->regs->RESET_REG, state->regs->RESET_VAL); - if (err < 0) - return err; - err = getPhy(state, state->regs->RESET_REG); - - /* - * Data sheet specifies reset period to be 2 msec. - * (I don't see any mention of the 2ms delay in the IP178C spec, only - * in IP175C, but it can't hurt.) - */ - mdelay(2); - } - - /* reset switch ports */ - for (i = 0; i < state->regs->NUM_PORTS-1; i++) { - err = ip_phy_write(state, i, MII_BMCR, BMCR_RESET); - if (err < 0) - return err; - } - - state->router_mode = 0; - state->vlan_enabled = 0; - ip17xx_reset_vlan_config(state); - - return state->regs->reset(state); -} - -static int ip17xx_get_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - - if (state->add_tag & (1<port_vlan)) { - if (state->remove_tag & (1<port_vlan)) - val->value.i = 3; // shouldn't ever happen. - else - val->value.i = 1; - } else { - if (state->remove_tag & (1<port_vlan)) - val->value.i = 0; - else - val->value.i = 2; - } - return 0; -} - -static int ip17xx_set_tagged(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - - state->add_tag &= ~(1<port_vlan); - state->remove_tag &= ~(1<port_vlan); - - if (val->value.i == 0) - state->remove_tag |= (1<port_vlan); - if (val->value.i == 1) - state->add_tag |= (1<port_vlan); - - return state->regs->update_state(state); -} - -/** Get the current phy address */ -static int ip17xx_get_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - - val->value.i = state->proc_mii.p; - return 0; -} - -/** Set a new phy address for low level access to registers */ -static int ip17xx_set_phy(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int new_reg = val->value.i; - - if (new_reg < 0 || new_reg > 31) - state->proc_mii.p = (u16)-1; - else - state->proc_mii.p = (u16)new_reg; - return 0; -} - -/** Get the current register number */ -static int ip17xx_get_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - - val->value.i = state->proc_mii.m; - return 0; -} - -/** Set a new register address for low level access to registers */ -static int ip17xx_set_reg(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int new_reg = val->value.i; - - if (new_reg < 0 || new_reg > 31) - state->proc_mii.m = (u16)-1; - else - state->proc_mii.m = (u16)new_reg; - return 0; -} - -/** Get the register content of state->proc_mii */ -static int ip17xx_get_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int retval = -EINVAL; - if (REG_SUPP(state->proc_mii)) - retval = getPhy(state, state->proc_mii); - - if (retval < 0) { - return retval; - } else { - val->value.i = retval; - return 0; - } -} - -/** Write a value to the register defined by phy/reg above */ -static int ip17xx_set_val(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int myval, err = -EINVAL; - - myval = val->value.i; - if (myval <= 0xffff && myval >= 0 && REG_SUPP(state->proc_mii)) { - err = setPhy(state, state->proc_mii, (u16)myval); - } - return err; -} - -static int ip17xx_read_name(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - val->value.s = state->regs->NAME; // Just a const pointer, won't be freed by swconfig. - return 0; -} - -static int ip17xx_get_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int vlan = val->port_vlan; - - if (vlan < 0 || vlan >= MAX_VLANS) - return -EINVAL; - - val->value.i = state->vlans[vlan].tag; - return 0; -} - -static int ip17xx_set_tag(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int vlan = val->port_vlan; - int tag = val->value.i; - - if (vlan < 0 || vlan >= MAX_VLANS) - return -EINVAL; - - if (tag < 0 || tag > 4095) - return -EINVAL; - - state->vlans[vlan].tag = tag; - return state->regs->update_state(state); -} - -static int ip17xx_set_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int nr = val->port_vlan; - int ctrl; - int autoneg; - int speed; - if (val->value.i == 100) { - speed = 1; - autoneg = 0; - } else if (val->value.i == 10) { - speed = 0; - autoneg = 0; - } else { - autoneg = 1; - speed = 1; - } - - /* Can't set speed for cpu port */ - if (nr == state->regs->CPU_PORT) - return -EINVAL; - - if (nr >= dev->ports || nr < 0) - return -EINVAL; - - ctrl = ip_phy_read(state, nr, 0); - if (ctrl < 0) - return -EIO; - - ctrl &= (~(1<<12)); - ctrl &= (~(1<<13)); - ctrl |= (autoneg<<12); - ctrl |= (speed<<13); - - return ip_phy_write(state, nr, 0, ctrl); -} - -static int ip17xx_get_port_speed(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int nr = val->port_vlan; - int speed, status; - - if (nr == state->regs->CPU_PORT) { - val->value.i = 100; - return 0; - } - - if (nr >= dev->ports || nr < 0) - return -EINVAL; - - status = ip_phy_read(state, nr, 1); - speed = ip_phy_read(state, nr, 18); - if (status < 0 || speed < 0) - return -EIO; - - if (status & 4) - val->value.i = ((speed & (1<<11)) ? 100 : 10); - else - val->value.i = 0; - - return 0; -} - -static int ip17xx_get_port_status(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct ip17xx_state *state = get_state(dev); - int ctrl, speed, status; - int nr = val->port_vlan; - int len; - char *buf = state->buf; // fixed-length at 80. - - if (nr == state->regs->CPU_PORT) { - sprintf(buf, "up, 100 Mbps, cpu port"); - val->value.s = buf; - return 0; - } - - if (nr >= dev->ports || nr < 0) - return -EINVAL; - - ctrl = ip_phy_read(state, nr, 0); - status = ip_phy_read(state, nr, 1); - speed = ip_phy_read(state, nr, 18); - if (ctrl < 0 || status < 0 || speed < 0) - return -EIO; - - if (status & 4) - len = sprintf(buf, "up, %d Mbps, %s duplex", - ((speed & (1<<11)) ? 100 : 10), - ((speed & (1<<10)) ? "full" : "half")); - else - len = sprintf(buf, "down"); - - if (ctrl & (1<<12)) { - len += sprintf(buf+len, ", auto-negotiate"); - if (!(status & (1<<5))) - len += sprintf(buf+len, " (in progress)"); - } else { - len += sprintf(buf+len, ", fixed speed (%d)", - ((ctrl & (1<<13)) ? 100 : 10)); - } - - buf[len] = '\0'; - val->value.s = buf; - return 0; -} - -static int ip17xx_get_pvid(struct switch_dev *dev, int port, int *val) -{ - struct ip17xx_state *state = get_state(dev); - - *val = state->ports[port].pvid; - return 0; -} - -static int ip17xx_set_pvid(struct switch_dev *dev, int port, int val) -{ - struct ip17xx_state *state = get_state(dev); - - if (val < 0 || val >= MAX_VLANS) - return -EINVAL; - - state->ports[port].pvid = val; - return state->regs->update_state(state); -} - - -enum Ports { - IP17XX_PORT_STATUS, - IP17XX_PORT_LINK, - IP17XX_PORT_TAGGED, - IP17XX_PORT_PVID, -}; - -enum Globals { - IP17XX_ENABLE_VLAN, - IP17XX_GET_NAME, - IP17XX_REGISTER_PHY, - IP17XX_REGISTER_MII, - IP17XX_REGISTER_VALUE, - IP17XX_REGISTER_ERRNO, -}; - -enum Vlans { - IP17XX_VLAN_TAG, -}; - -static const struct switch_attr ip17xx_global[] = { - [IP17XX_ENABLE_VLAN] = { - .id = IP17XX_ENABLE_VLAN, - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Flag to enable or disable VLANs and tagging", - .get = ip17xx_get_enable_vlan, - .set = ip17xx_set_enable_vlan, - }, - [IP17XX_GET_NAME] = { - .id = IP17XX_GET_NAME, - .type = SWITCH_TYPE_STRING, - .description = "Returns the type of IC+ chip.", - .name = "name", - .get = ip17xx_read_name, - .set = NULL, - }, - /* jal: added for low level debugging etc. */ - [IP17XX_REGISTER_PHY] = { - .id = IP17XX_REGISTER_PHY, - .type = SWITCH_TYPE_INT, - .description = "Direct register access: set PHY (0-4, or 29,30,31)", - .name = "phy", - .get = ip17xx_get_phy, - .set = ip17xx_set_phy, - }, - [IP17XX_REGISTER_MII] = { - .id = IP17XX_REGISTER_MII, - .type = SWITCH_TYPE_INT, - .description = "Direct register access: set MII register number (0-31)", - .name = "reg", - .get = ip17xx_get_reg, - .set = ip17xx_set_reg, - }, - [IP17XX_REGISTER_VALUE] = { - .id = IP17XX_REGISTER_VALUE, - .type = SWITCH_TYPE_INT, - .description = "Direct register access: read/write to register (0-65535)", - .name = "val", - .get = ip17xx_get_val, - .set = ip17xx_set_val, - }, -}; - -static const struct switch_attr ip17xx_vlan[] = { - [IP17XX_VLAN_TAG] = { - .id = IP17XX_VLAN_TAG, - .type = SWITCH_TYPE_INT, - .description = "VLAN ID (0-4095) [IP175D only]", - .name = "vid", - .get = ip17xx_get_tag, - .set = ip17xx_set_tag, - } -}; - -static const struct switch_attr ip17xx_port[] = { - [IP17XX_PORT_STATUS] = { - .id = IP17XX_PORT_STATUS, - .type = SWITCH_TYPE_STRING, - .description = "Returns Detailed port status", - .name = "status", - .get = ip17xx_get_port_status, - .set = NULL, - }, - [IP17XX_PORT_LINK] = { - .id = IP17XX_PORT_LINK, - .type = SWITCH_TYPE_INT, - .description = "Link speed. Can write 0 for auto-negotiate, or 10 or 100", - .name = "link", - .get = ip17xx_get_port_speed, - .set = ip17xx_set_port_speed, - }, - [IP17XX_PORT_TAGGED] = { - .id = IP17XX_PORT_LINK, - .type = SWITCH_TYPE_INT, - .description = "0 = untag, 1 = add tags, 2 = do not alter (This value is reset if vlans are altered)", - .name = "tagged", - .get = ip17xx_get_tagged, - .set = ip17xx_set_tagged, - }, -}; - -static const struct switch_dev_ops ip17xx_ops = { - .attr_global = { - .attr = ip17xx_global, - .n_attr = ARRAY_SIZE(ip17xx_global), - }, - .attr_port = { - .attr = ip17xx_port, - .n_attr = ARRAY_SIZE(ip17xx_port), - }, - .attr_vlan = { - .attr = ip17xx_vlan, - .n_attr = ARRAY_SIZE(ip17xx_vlan), - }, - - .get_port_pvid = ip17xx_get_pvid, - .set_port_pvid = ip17xx_set_pvid, - .get_vlan_ports = ip17xx_get_ports, - .set_vlan_ports = ip17xx_set_ports, - .apply_config = ip17xx_apply, - .reset_switch = ip17xx_reset, -}; - -static int ip17xx_probe(struct phy_device *pdev) -{ - struct ip17xx_state *state; - struct switch_dev *dev; - int err; - - /* We only attach to PHY 0, but use all available PHYs */ - if (pdev->mdio.addr != 0) - return -ENODEV; - - state = kzalloc(sizeof(*state), GFP_KERNEL); - if (!state) - return -ENOMEM; - - dev = &state->dev; - - pdev->priv = state; - state->mii_bus = pdev->mdio.bus; - - err = get_model(state); - if (err < 0) - goto error; - - dev->vlans = MAX_VLANS; - dev->cpu_port = state->regs->CPU_PORT; - dev->ports = state->regs->NUM_PORTS; - dev->name = state->regs->NAME; - dev->ops = &ip17xx_ops; - - pr_info("IP17xx: Found %s at %s\n", dev->name, dev_name(&pdev->mdio.dev)); - return 0; - -error: - kfree(state); - return err; -} - -static int ip17xx_config_init(struct phy_device *pdev) -{ - struct ip17xx_state *state = pdev->priv; - struct net_device *dev = pdev->attached_dev; - int err; - - err = register_switch(&state->dev, dev); - if (err < 0) - return err; - - state->registered = true; - ip17xx_reset(&state->dev); - return 0; -} - -static void ip17xx_remove(struct phy_device *pdev) -{ - struct ip17xx_state *state = pdev->priv; - - if (state->registered) - unregister_switch(&state->dev); - kfree(state); -} - -static int ip17xx_config_aneg(struct phy_device *pdev) -{ - return 0; -} - -static int ip17xx_aneg_done(struct phy_device *pdev) -{ - return 1; /* Return any positive value */ -} - -static int ip17xx_update_link(struct phy_device *pdev) -{ - pdev->link = 1; - return 0; -} - -static int ip17xx_read_status(struct phy_device *pdev) -{ - pdev->speed = SPEED_100; - pdev->duplex = DUPLEX_FULL; - pdev->pause = pdev->asym_pause = 0; - pdev->link = 1; - - return 0; -} - -static struct phy_driver ip17xx_driver[] = { - { - .name = "IC+ IP17xx", - .phy_id = 0x02430c00, - .phy_id_mask = 0x0ffffc00, - .features = PHY_BASIC_FEATURES, - .probe = ip17xx_probe, - .remove = ip17xx_remove, - .config_init = ip17xx_config_init, - .config_aneg = ip17xx_config_aneg, - .aneg_done = ip17xx_aneg_done, - .update_link = ip17xx_update_link, - .read_status = ip17xx_read_status, - } -}; - -module_phy_driver(ip17xx_driver); - -MODULE_AUTHOR("Patrick Horn "); -MODULE_AUTHOR("Felix Fietkau "); -MODULE_AUTHOR("Martin Mares "); -MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/mvsw61xx.c b/target/linux/generic/files/drivers/net/phy/mvsw61xx.c deleted file mode 100644 index 9a689e6c81..0000000000 --- a/target/linux/generic/files/drivers/net/phy/mvsw61xx.c +++ /dev/null @@ -1,947 +0,0 @@ -/* - * Marvell 88E61xx switch driver - * - * Copyright (c) 2014 Claudio Leite - * Copyright (c) 2014 Nikita Nazarenko - * - * Based on code (c) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "mvsw61xx.h" - -MODULE_DESCRIPTION("Marvell 88E61xx Switch driver"); -MODULE_AUTHOR("Claudio Leite "); -MODULE_AUTHOR("Nikita Nazarenko "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:mvsw61xx"); - -/* - * Register access is done through direct or indirect addressing, - * depending on how the switch is physically connected. - * - * Direct addressing: all port and global registers directly - * accessible via an address/register pair - * - * Indirect addressing: switch is mapped at a single address, - * port and global registers accessible via a single command/data - * register pair - */ - -static int -mvsw61xx_wait_mask_raw(struct mii_bus *bus, int addr, - int reg, u16 mask, u16 val) -{ - int i = 100; - u16 r; - - do { - r = bus->read(bus, addr, reg); - if ((r & mask) == val) - return 0; - } while (--i > 0); - - return -ETIMEDOUT; -} - -static u16 -r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) -{ - u16 ind_addr; - - if (!indirect) - return bus->read(bus, addr, reg); - - /* Indirect read: First, make sure switch is free */ - mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, - MV_INDIRECT_INPROGRESS, 0); - - /* Load address and request read */ - ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; - bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, - ind_addr); - - /* Wait until it's ready */ - mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, - MV_INDIRECT_INPROGRESS, 0); - - /* Read the requested data */ - return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); -} - -static void -w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, - int reg, u16 val) -{ - u16 ind_addr; - - if (!indirect) { - bus->write(bus, addr, reg, val); - return; - } - - /* Indirect write: First, make sure switch is free */ - mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, - MV_INDIRECT_INPROGRESS, 0); - - /* Load the data to be written */ - bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); - - /* Wait again for switch to be free */ - mvsw61xx_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, - MV_INDIRECT_INPROGRESS, 0); - - /* Load address, and issue write command */ - ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; - bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, - ind_addr); -} - -/* swconfig support */ - -static inline u16 -sr16(struct switch_dev *dev, int addr, int reg) -{ - struct mvsw61xx_state *state = get_state(dev); - - return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); -} - -static inline void -sw16(struct switch_dev *dev, int addr, int reg, u16 val) -{ - struct mvsw61xx_state *state = get_state(dev); - - w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); -} - -static int -mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr, - int reg, u16 mask, u16 val) -{ - int i = 100; - u16 r; - - do { - r = sr16(dev, addr, reg) & mask; - if (r == val) - return 0; - } while (--i > 0); - - return -ETIMEDOUT; -} - -static int -mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg) -{ - sw16(dev, MV_GLOBAL2REG(SMI_OP), - MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg); - - if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), - MV_INDIRECT_INPROGRESS, 0) < 0) - return -ETIMEDOUT; - - return sr16(dev, MV_GLOBAL2REG(SMI_DATA)); -} - -static int -mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val) -{ - sw16(dev, MV_GLOBAL2REG(SMI_DATA), val); - - sw16(dev, MV_GLOBAL2REG(SMI_OP), - MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg); - - return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP), - MV_INDIRECT_INPROGRESS, 0) < 0; -} - -static int -mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg) -{ - int ret; - - mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); - ret = mvsw61xx_mdio_read(dev, port, reg); - mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); - - return ret; -} - -static void -mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg, - u16 val) -{ - mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page); - mvsw61xx_mdio_write(dev, port, reg, val); - mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0); -} - -static int -mvsw61xx_get_port_mask(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - char *buf = state->buf; - int port, len, i; - u16 reg; - - port = val->port_vlan; - reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; - - len = sprintf(buf, "0x%04x: ", reg); - - for (i = 0; i < MV_PORTS; i++) { - if (reg & (1 << i)) - len += sprintf(buf + len, "%d ", i); - else if (i == port) - len += sprintf(buf + len, "(%d) ", i); - } - - val->value.s = buf; - - return 0; -} - -static int -mvsw61xx_get_port_qmode(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - - val->value.i = state->ports[val->port_vlan].qmode; - - return 0; -} - -static int -mvsw61xx_set_port_qmode(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - - state->ports[val->port_vlan].qmode = val->value.i; - - return 0; -} - -static int -mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val) -{ - struct mvsw61xx_state *state = get_state(dev); - - *val = state->ports[port].pvid; - - return 0; -} - -static int -mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val) -{ - struct mvsw61xx_state *state = get_state(dev); - - if (val < 0 || val >= MV_VLANS) - return -EINVAL; - - state->ports[port].pvid = (u16)val; - - return 0; -} - -static int -mvsw61xx_get_port_link(struct switch_dev *dev, int port, - struct switch_port_link *link) -{ - u16 status, speed; - - status = sr16(dev, MV_PORTREG(STATUS, port)); - - link->link = status & MV_PORT_STATUS_LINK; - if (!link->link) - return 0; - - link->duplex = status & MV_PORT_STATUS_FDX; - - speed = (status & MV_PORT_STATUS_SPEED_MASK) >> - MV_PORT_STATUS_SPEED_SHIFT; - - switch (speed) { - case MV_PORT_STATUS_SPEED_10: - link->speed = SWITCH_PORT_SPEED_10; - break; - case MV_PORT_STATUS_SPEED_100: - link->speed = SWITCH_PORT_SPEED_100; - break; - case MV_PORT_STATUS_SPEED_1000: - link->speed = SWITCH_PORT_SPEED_1000; - break; - } - - return 0; -} - -static int mvsw61xx_get_vlan_ports(struct switch_dev *dev, - struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int i, j, mode, vno; - - vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - for (i = 0, j = 0; i < dev->ports; i++) { - if (state->vlans[vno].mask & (1 << i)) { - val->value.ports[j].id = i; - - mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; - if (mode == MV_VTUCTL_EGRESS_TAGGED) - val->value.ports[j].flags = - (1 << SWITCH_PORT_FLAG_TAGGED); - else - val->value.ports[j].flags = 0; - - j++; - } - } - - val->len = j; - - return 0; -} - -static int mvsw61xx_set_vlan_ports(struct switch_dev *dev, - struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int i, mode, pno, vno; - - vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - state->vlans[vno].mask = 0; - state->vlans[vno].port_mode = 0; - state->vlans[vno].port_sstate = 0; - - if(state->vlans[vno].vid == 0) - state->vlans[vno].vid = vno; - - for (i = 0; i < val->len; i++) { - pno = val->value.ports[i].id; - - state->vlans[vno].mask |= (1 << pno); - if (val->value.ports[i].flags & - (1 << SWITCH_PORT_FLAG_TAGGED)) - mode = MV_VTUCTL_EGRESS_TAGGED; - else - mode = MV_VTUCTL_EGRESS_UNTAGGED; - - state->vlans[vno].port_mode |= mode << (pno * 4); - state->vlans[vno].port_sstate |= - MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2); - } - - /* - * DISCARD is nonzero, so it must be explicitly - * set on ports not in the VLAN. - */ - for (i = 0; i < dev->ports; i++) - if (!(state->vlans[vno].mask & (1 << i))) - state->vlans[vno].port_mode |= - MV_VTUCTL_DISCARD << (i * 4); - - return 0; -} - -static int mvsw61xx_get_vlan_port_based(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - if (state->vlans[vno].port_based) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int mvsw61xx_set_vlan_port_based(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - if (val->value.i == 1) - state->vlans[vno].port_based = true; - else - state->vlans[vno].port_based = false; - - return 0; -} - -static int mvsw61xx_get_vid(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - val->value.i = state->vlans[vno].vid; - - return 0; -} - -static int mvsw61xx_set_vid(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - int vno = val->port_vlan; - - if (vno <= 0 || vno >= dev->vlans) - return -EINVAL; - - state->vlans[vno].vid = val->value.i; - - return 0; -} - -static int mvsw61xx_get_enable_vlan(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - - val->value.i = state->vlan_enabled; - - return 0; -} - -static int mvsw61xx_set_enable_vlan(struct switch_dev *dev, - const struct switch_attr *attr, struct switch_val *val) -{ - struct mvsw61xx_state *state = get_state(dev); - - state->vlan_enabled = val->value.i; - - return 0; -} - -static int mvsw61xx_vtu_program(struct switch_dev *dev) -{ - struct mvsw61xx_state *state = get_state(dev); - u16 v1, v2, s1, s2; - int i; - - /* Flush */ - mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS, 0); - sw16(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE); - - /* Write VLAN table */ - for (i = 1; i < dev->vlans; i++) { - if (state->vlans[i].mask == 0 || - state->vlans[i].vid == 0 || - state->vlans[i].port_based == true) - continue; - - mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS, 0); - - /* Write per-VLAN port state into STU */ - s1 = (u16) (state->vlans[i].port_sstate & 0xffff); - s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff); - - sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID); - sw16(dev, MV_GLOBALREG(VTU_SID), i); - sw16(dev, MV_GLOBALREG(VTU_DATA1), s1); - sw16(dev, MV_GLOBALREG(VTU_DATA2), s2); - sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); - - sw16(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD); - mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS, 0); - - /* Write VLAN information into VTU */ - v1 = (u16) (state->vlans[i].port_mode & 0xffff); - v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff); - - sw16(dev, MV_GLOBALREG(VTU_VID), - MV_VTU_VID_VALID | state->vlans[i].vid); - sw16(dev, MV_GLOBALREG(VTU_SID), i); - sw16(dev, MV_GLOBALREG(VTU_FID), i); - sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); - sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); - sw16(dev, MV_GLOBALREG(VTU_DATA3), 0); - - sw16(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); - mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), - MV_VTUOP_INPROGRESS, 0); - } - - return 0; -} - -static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno) -{ - struct mvsw61xx_state *state = get_state(dev); - int i, mode; - - for (i = 0; i < dev->ports; i++) { - if (!(state->vlans[vno].mask & (1 << i))) - continue; - - mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; - - if(mode != MV_VTUCTL_EGRESS_TAGGED) - state->ports[i].pvid = state->vlans[vno].vid; - - if (state->vlans[vno].port_based) { - state->ports[i].mask |= state->vlans[vno].mask; - state->ports[i].fdb = vno; - } - else - state->ports[i].qmode = MV_8021Q_MODE_SECURE; - } -} - -static int mvsw61xx_update_state(struct switch_dev *dev) -{ - struct mvsw61xx_state *state = get_state(dev); - int i; - u16 reg; - - if (!state->registered) - return -EINVAL; - - /* - * Set 802.1q-only mode if vlan_enabled is true. - * - * Without this, even if 802.1q is enabled for - * a port/VLAN, it still depends on the port-based - * VLAN mask being set. - * - * With this setting, port-based VLANs are still - * functional, provided the VID is not in the VTU. - */ - reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); - - if (state->vlan_enabled) - reg |= MV_8021Q_VLAN_ONLY; - else - reg &= ~MV_8021Q_VLAN_ONLY; - - sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); - - /* - * Set port-based VLAN masks on each port - * based only on VLAN definitions known to - * the driver (i.e. in state). - * - * This means any pre-existing port mapping is - * wiped out once our driver is initialized. - */ - for (i = 0; i < dev->ports; i++) { - state->ports[i].mask = 0; - state->ports[i].qmode = MV_8021Q_MODE_DISABLE; - } - - for (i = 0; i < dev->vlans; i++) - mvsw61xx_vlan_port_config(dev, i); - - for (i = 0; i < dev->ports; i++) { - reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; - reg |= state->ports[i].pvid; - sw16(dev, MV_PORTREG(VLANID, i), reg); - - state->ports[i].mask &= ~(1 << i); - - /* set default forwarding DB number and port mask */ - reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK; - reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) & - MV_FDB_HI_MASK; - sw16(dev, MV_PORTREG(CONTROL1, i), reg); - - reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) | - state->ports[i].mask; - sw16(dev, MV_PORTREG(VLANMAP, i), reg); - - reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & - ~MV_8021Q_MODE_MASK; - reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; - sw16(dev, MV_PORTREG(CONTROL2, i), reg); - } - - mvsw61xx_vtu_program(dev); - - return 0; -} - -static int mvsw61xx_apply(struct switch_dev *dev) -{ - return mvsw61xx_update_state(dev); -} - -static void mvsw61xx_enable_serdes(struct switch_dev *dev) -{ - int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES, - MV_PAGE_FIBER_SERDES, MII_BMCR); - if (bmcr < 0) - return; - - if (bmcr & BMCR_PDOWN) - mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES, - MV_PAGE_FIBER_SERDES, MII_BMCR, - bmcr & ~BMCR_PDOWN); -} - -static int _mvsw61xx_reset(struct switch_dev *dev, bool full) -{ - struct mvsw61xx_state *state = get_state(dev); - int i; - u16 reg; - - /* Disable all ports before reset */ - for (i = 0; i < dev->ports; i++) { - reg = sr16(dev, MV_PORTREG(CONTROL, i)) & - ~MV_PORTCTRL_FORWARDING; - sw16(dev, MV_PORTREG(CONTROL, i), reg); - } - - reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; - - sw16(dev, MV_GLOBALREG(CONTROL), reg); - if (mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(CONTROL), - MV_CONTROL_RESET, 0) < 0) - return -ETIMEDOUT; - - for (i = 0; i < dev->ports; i++) { - state->ports[i].fdb = 0; - state->ports[i].qmode = 0; - state->ports[i].mask = 0; - state->ports[i].pvid = 0; - - /* Force flow control off */ - reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK; - reg |= MV_PHYCTL_FC_DISABLE; - sw16(dev, MV_PORTREG(PHYCTL, i), reg); - - /* Set port association vector */ - sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); - - /* power up phys */ - if (full && i < 5) { - mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL, - MV_SPEC_MDI_CROSS_AUTO | - MV_SPEC_ENERGY_DETECT | - MV_SPEC_DOWNSHIFT_COUNTER); - mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET | - BMCR_ANENABLE | BMCR_FULLDPLX | - BMCR_SPEED1000); - } - - /* enable SerDes if necessary */ - if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) { - u16 sts = sr16(dev, MV_PORTREG(STATUS, i)); - u16 mode = sts & MV_PORT_STATUS_CMODE_MASK; - - if (mode == MV_PORT_STATUS_CMODE_100BASE_X || - mode == MV_PORT_STATUS_CMODE_1000BASE_X || - mode == MV_PORT_STATUS_CMODE_SGMII) { - mvsw61xx_enable_serdes(dev); - } - } - } - - for (i = 0; i < dev->vlans; i++) { - state->vlans[i].port_based = false; - state->vlans[i].mask = 0; - state->vlans[i].vid = 0; - state->vlans[i].port_mode = 0; - state->vlans[i].port_sstate = 0; - } - - state->vlan_enabled = 0; - - mvsw61xx_update_state(dev); - - /* Re-enable ports */ - for (i = 0; i < dev->ports; i++) { - reg = sr16(dev, MV_PORTREG(CONTROL, i)) | - MV_PORTCTRL_FORWARDING; - sw16(dev, MV_PORTREG(CONTROL, i), reg); - } - - return 0; -} - -static int mvsw61xx_reset(struct switch_dev *dev) -{ - return _mvsw61xx_reset(dev, false); -} - -enum { - MVSW61XX_ENABLE_VLAN, -}; - -enum { - MVSW61XX_VLAN_PORT_BASED, - MVSW61XX_VLAN_ID, -}; - -enum { - MVSW61XX_PORT_MASK, - MVSW61XX_PORT_QMODE, -}; - -static const struct switch_attr mvsw61xx_global[] = { - [MVSW61XX_ENABLE_VLAN] = { - .id = MVSW61XX_ENABLE_VLAN, - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable 802.1q VLAN support", - .get = mvsw61xx_get_enable_vlan, - .set = mvsw61xx_set_enable_vlan, - }, -}; - -static const struct switch_attr mvsw61xx_vlan[] = { - [MVSW61XX_VLAN_PORT_BASED] = { - .id = MVSW61XX_VLAN_PORT_BASED, - .type = SWITCH_TYPE_INT, - .name = "port_based", - .description = "Use port-based (non-802.1q) VLAN only", - .get = mvsw61xx_get_vlan_port_based, - .set = mvsw61xx_set_vlan_port_based, - }, - [MVSW61XX_VLAN_ID] = { - .id = MVSW61XX_VLAN_ID, - .type = SWITCH_TYPE_INT, - .name = "vid", - .description = "Get/set VLAN ID", - .get = mvsw61xx_get_vid, - .set = mvsw61xx_set_vid, - }, -}; - -static const struct switch_attr mvsw61xx_port[] = { - [MVSW61XX_PORT_MASK] = { - .id = MVSW61XX_PORT_MASK, - .type = SWITCH_TYPE_STRING, - .description = "Port-based VLAN mask", - .name = "mask", - .get = mvsw61xx_get_port_mask, - .set = NULL, - }, - [MVSW61XX_PORT_QMODE] = { - .id = MVSW61XX_PORT_QMODE, - .type = SWITCH_TYPE_INT, - .description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", - .name = "qmode", - .get = mvsw61xx_get_port_qmode, - .set = mvsw61xx_set_port_qmode, - }, -}; - -static const struct switch_dev_ops mvsw61xx_ops = { - .attr_global = { - .attr = mvsw61xx_global, - .n_attr = ARRAY_SIZE(mvsw61xx_global), - }, - .attr_vlan = { - .attr = mvsw61xx_vlan, - .n_attr = ARRAY_SIZE(mvsw61xx_vlan), - }, - .attr_port = { - .attr = mvsw61xx_port, - .n_attr = ARRAY_SIZE(mvsw61xx_port), - }, - .get_port_link = mvsw61xx_get_port_link, - .get_port_pvid = mvsw61xx_get_port_pvid, - .set_port_pvid = mvsw61xx_set_port_pvid, - .get_vlan_ports = mvsw61xx_get_vlan_ports, - .set_vlan_ports = mvsw61xx_set_vlan_ports, - .apply_config = mvsw61xx_apply, - .reset_switch = mvsw61xx_reset, -}; - -/* end swconfig stuff */ - -static int mvsw61xx_probe(struct platform_device *pdev) -{ - struct mvsw61xx_state *state; - struct device_node *np = pdev->dev.of_node; - struct device_node *mdio; - char *model_str; - u32 val; - int err; - - state = kzalloc(sizeof(*state), GFP_KERNEL); - if (!state) - return -ENOMEM; - - mdio = of_parse_phandle(np, "mii-bus", 0); - if (!mdio) { - dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); - err = -ENODEV; - goto out_err; - } - - state->bus = of_mdio_find_bus(mdio); - if (!state->bus) { - dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); - err = -ENODEV; - goto out_err; - } - - state->is_indirect = of_property_read_bool(np, "is-indirect"); - - if (state->is_indirect) { - if (of_property_read_u32(np, "reg", &val)) { - dev_err(&pdev->dev, "Switch address not specified\n"); - err = -ENODEV; - goto out_err; - } - - state->base_addr = val; - } else { - state->base_addr = MV_BASE; - } - - state->model = r16(state->bus, state->is_indirect, state->base_addr, - MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; - - switch(state->model) { - case MV_IDENT_VALUE_6171: - model_str = MV_IDENT_STR_6171; - break; - case MV_IDENT_VALUE_6172: - model_str = MV_IDENT_STR_6172; - break; - case MV_IDENT_VALUE_6176: - model_str = MV_IDENT_STR_6176; - break; - case MV_IDENT_VALUE_6352: - model_str = MV_IDENT_STR_6352; - break; - default: - dev_err(&pdev->dev, "No compatible switch found at 0x%02x\n", - state->base_addr); - err = -ENODEV; - goto out_err; - } - - platform_set_drvdata(pdev, state); - dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str, - state->bus->id, state->base_addr); - - dev_info(&pdev->dev, "Using %sdirect addressing\n", - (state->is_indirect ? "in" : "")); - - if (of_property_read_u32(np, "cpu-port-0", &val)) { - dev_err(&pdev->dev, "CPU port not set\n"); - err = -ENODEV; - goto out_err; - } - - state->cpu_port0 = val; - - if (!of_property_read_u32(np, "cpu-port-1", &val)) - state->cpu_port1 = val; - else - state->cpu_port1 = -1; - - state->dev.vlans = MV_VLANS; - state->dev.cpu_port = state->cpu_port0; - state->dev.ports = MV_PORTS; - state->dev.name = model_str; - state->dev.ops = &mvsw61xx_ops; - state->dev.alias = dev_name(&pdev->dev); - - _mvsw61xx_reset(&state->dev, true); - - err = register_switch(&state->dev, NULL); - if (err < 0) - goto out_err; - - state->registered = true; - - return 0; -out_err: - kfree(state); - return err; -} - -static int -mvsw61xx_remove(struct platform_device *pdev) -{ - struct mvsw61xx_state *state = platform_get_drvdata(pdev); - - if (state->registered) - unregister_switch(&state->dev); - - kfree(state); - - return 0; -} - -static const struct of_device_id mvsw61xx_match[] = { - { .compatible = "marvell,88e6171" }, - { .compatible = "marvell,88e6172" }, - { .compatible = "marvell,88e6176" }, - { .compatible = "marvell,88e6352" }, - { } -}; -MODULE_DEVICE_TABLE(of, mvsw61xx_match); - -static struct platform_driver mvsw61xx_driver = { - .probe = mvsw61xx_probe, - .remove = mvsw61xx_remove, - .driver = { - .name = "mvsw61xx", - .of_match_table = of_match_ptr(mvsw61xx_match), - .owner = THIS_MODULE, - }, -}; - -static int __init mvsw61xx_module_init(void) -{ - return platform_driver_register(&mvsw61xx_driver); -} -late_initcall(mvsw61xx_module_init); - -static void __exit mvsw61xx_module_exit(void) -{ - platform_driver_unregister(&mvsw61xx_driver); -} -module_exit(mvsw61xx_module_exit); diff --git a/target/linux/generic/files/drivers/net/phy/mvsw61xx.h b/target/linux/generic/files/drivers/net/phy/mvsw61xx.h deleted file mode 100644 index a07b09cd14..0000000000 --- a/target/linux/generic/files/drivers/net/phy/mvsw61xx.h +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Marvell 88E61xx switch driver - * - * Copyright (c) 2014 Claudio Leite - * Copyright (c) 2014 Nikita Nazarenko - * - * Based on code (c) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ - -#ifndef __MVSW61XX_H -#define __MVSW61XX_H - -#define MV_PORTS 7 -#define MV_PORTS_MASK ((1 << MV_PORTS) - 1) - -#define MV_BASE 0x10 - -#define MV_SWITCHPORT_BASE 0x10 -#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) -#define MV_SWITCHREGS (MV_BASE + 0xb) - -#define MV_VLANS 64 - -enum { - MV_PORT_STATUS = 0x00, - MV_PORT_PHYCTL = 0x01, - MV_PORT_JAMCTL = 0x02, - MV_PORT_IDENT = 0x03, - MV_PORT_CONTROL = 0x04, - MV_PORT_CONTROL1 = 0x05, - MV_PORT_VLANMAP = 0x06, - MV_PORT_VLANID = 0x07, - MV_PORT_CONTROL2 = 0x08, - MV_PORT_ASSOC = 0x0b, - MV_PORT_RX_DISCARD_LOW = 0x10, - MV_PORT_RX_DISCARD_HIGH = 0x11, - MV_PORT_IN_FILTERED = 0x12, - MV_PORT_OUT_ACCEPTED = 0x13, -}; -#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type - -enum { - MV_PORT_STATUS_FDX = (1 << 10), - MV_PORT_STATUS_LINK = (1 << 11), -}; - -enum { - MV_PORT_STATUS_CMODE_100BASE_X = 0x8, - MV_PORT_STATUS_CMODE_1000BASE_X = 0x9, - MV_PORT_STATUS_CMODE_SGMII = 0xa, -}; - -#define MV_PORT_STATUS_CMODE_MASK 0xf - -enum { - MV_PORT_STATUS_SPEED_10 = 0x00, - MV_PORT_STATUS_SPEED_100 = 0x01, - MV_PORT_STATUS_SPEED_1000 = 0x02, -}; -#define MV_PORT_STATUS_SPEED_SHIFT 8 -#define MV_PORT_STATUS_SPEED_MASK (3 << 8) - -enum { - MV_PORTCTRL_DISABLED = (0 << 0), - MV_PORTCTRL_BLOCKING = (1 << 0), - MV_PORTCTRL_LEARNING = (2 << 0), - MV_PORTCTRL_FORWARDING = (3 << 0), - MV_PORTCTRL_VLANTUN = (1 << 7), - MV_PORTCTRL_EGRESS = (1 << 12), -}; - -#define MV_PHYCTL_FC_MASK (3 << 6) - -enum { - MV_PHYCTL_FC_ENABLE = (3 << 6), - MV_PHYCTL_FC_DISABLE = (1 << 6), -}; - -enum { - MV_8021Q_EGRESS_UNMODIFIED = 0x00, - MV_8021Q_EGRESS_UNTAGGED = 0x01, - MV_8021Q_EGRESS_TAGGED = 0x02, - MV_8021Q_EGRESS_ADDTAG = 0x03, -}; - -#define MV_8021Q_MODE_SHIFT 10 -#define MV_8021Q_MODE_MASK (0x3 << MV_8021Q_MODE_SHIFT) - -enum { - MV_8021Q_MODE_DISABLE = 0x00, - MV_8021Q_MODE_FALLBACK = 0x01, - MV_8021Q_MODE_CHECK = 0x02, - MV_8021Q_MODE_SECURE = 0x03, -}; - -enum { - MV_8021Q_VLAN_ONLY = (1 << 15), -}; - -#define MV_PORTASSOC_MONITOR (1 << 15) - -enum { - MV_SWITCH_ATU_FID0 = 0x01, - MV_SWITCH_ATU_FID1 = 0x02, - MV_SWITCH_ATU_SID = 0x03, - MV_SWITCH_CTRL = 0x04, - MV_SWITCH_ATU_CTRL = 0x0a, - MV_SWITCH_ATU_OP = 0x0b, - MV_SWITCH_ATU_DATA = 0x0c, - MV_SWITCH_ATU_MAC0 = 0x0d, - MV_SWITCH_ATU_MAC1 = 0x0e, - MV_SWITCH_ATU_MAC2 = 0x0f, - MV_SWITCH_GLOBAL = 0x1b, - MV_SWITCH_GLOBAL2 = 0x1c, -}; -#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type - -enum { - MV_SWITCHCTL_EEIE = (1 << 0), - MV_SWITCHCTL_PHYIE = (1 << 1), - MV_SWITCHCTL_ATUDONE = (1 << 2), - MV_SWITCHCTL_ATUIE = (1 << 3), - MV_SWITCHCTL_CTRMODE = (1 << 8), - MV_SWITCHCTL_RELOAD = (1 << 9), - MV_SWITCHCTL_MSIZE = (1 << 10), - MV_SWITCHCTL_DROP = (1 << 13), -}; - -enum { -#define MV_ATUCTL_AGETIME_MIN 16 -#define MV_ATUCTL_AGETIME_MAX 4080 -#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) - MV_ATUCTL_ATU_256 = (0 << 12), - MV_ATUCTL_ATU_512 = (1 << 12), - MV_ATUCTL_ATU_1K = (2 << 12), - MV_ATUCTL_ATUMASK = (3 << 12), - MV_ATUCTL_NO_LEARN = (1 << 14), - MV_ATUCTL_RESET = (1 << 15), -}; - -enum { -#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) - MV_ATUOP_NOOP = (0 << 12), - MV_ATUOP_FLUSH_ALL = (1 << 12), - MV_ATUOP_FLUSH_U = (2 << 12), - MV_ATUOP_LOAD_DB = (3 << 12), - MV_ATUOP_GET_NEXT = (4 << 12), - MV_ATUOP_FLUSH_DB = (5 << 12), - MV_ATUOP_FLUSH_DB_UU = (6 << 12), - MV_ATUOP_INPROGRESS = (1 << 15), -}; - -enum { - MV_GLOBAL_STATUS = 0x00, - MV_GLOBAL_ATU_FID = 0x01, - MV_GLOBAL_VTU_FID = 0x02, - MV_GLOBAL_VTU_SID = 0x03, - MV_GLOBAL_CONTROL = 0x04, - MV_GLOBAL_VTU_OP = 0x05, - MV_GLOBAL_VTU_VID = 0x06, - MV_GLOBAL_VTU_DATA1 = 0x07, - MV_GLOBAL_VTU_DATA2 = 0x08, - MV_GLOBAL_VTU_DATA3 = 0x09, - MV_GLOBAL_CONTROL2 = 0x1c, -}; -#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type - -enum { - MV_GLOBAL2_SMI_OP = 0x18, - MV_GLOBAL2_SMI_DATA = 0x19, - MV_GLOBAL2_SDET_POLARITY = 0x1d, -}; -#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type - -enum { - MV_VTU_VID_VALID = (1 << 12), -}; - -enum { - MV_VTUOP_PURGE = (1 << 12), - MV_VTUOP_LOAD = (3 << 12), - MV_VTUOP_INPROGRESS = (1 << 15), - MV_VTUOP_STULOAD = (5 << 12), - MV_VTUOP_VTU_GET_NEXT = (4 << 12), - MV_VTUOP_STU_GET_NEXT = (6 << 12), - MV_VTUOP_GET_VIOLATION = (7 << 12), -}; - -enum { - MV_CONTROL_RESET = (1 << 15), - MV_CONTROL_PPU_ENABLE = (1 << 14), -}; - -enum { - MV_VTUCTL_EGRESS_UNMODIFIED = (0 << 0), - MV_VTUCTL_EGRESS_UNTAGGED = (1 << 0), - MV_VTUCTL_EGRESS_TAGGED = (2 << 0), - MV_VTUCTL_DISCARD = (3 << 0), -}; - -enum { - MV_STUCTL_STATE_DISABLED = (0 << 0), - MV_STUCTL_STATE_BLOCKING = (1 << 0), - MV_STUCTL_STATE_LEARNING = (2 << 0), - MV_STUCTL_STATE_FORWARDING = (3 << 0), -}; - -enum { - MV_INDIRECT_REG_CMD = 0, - MV_INDIRECT_REG_DATA = 1, -}; - -enum { - MV_INDIRECT_INPROGRESS = 0x8000, - MV_INDIRECT_WRITE = 0x9400, - MV_INDIRECT_READ = 0x9800, -}; -#define MV_INDIRECT_ADDR_S 5 - -#define MV_IDENT_MASK 0xfff0 - -#define MV_IDENT_VALUE_6171 0x1710 -#define MV_IDENT_STR_6171 "MV88E6171" - -#define MV_IDENT_VALUE_6172 0x1720 -#define MV_IDENT_STR_6172 "MV88E6172" - -#define MV_IDENT_VALUE_6176 0x1760 -#define MV_IDENT_STR_6176 "MV88E6176" - -#define MV_IDENT_VALUE_6352 0x3520 -#define MV_IDENT_STR_6352 "MV88E6352" - -#define MV_PVID_MASK 0x0fff - -#define MV_FDB_HI_MASK 0x00ff -#define MV_FDB_LO_MASK 0xf000 -#define MV_FDB_HI_SHIFT 4 -#define MV_FDB_LO_SHIFT 12 - -/* Marvell Specific PHY register */ -#define MII_MV_SPEC_CTRL 16 -enum { - MV_SPEC_MDI_CROSS_AUTO = (0x6 << 4), - MV_SPEC_ENERGY_DETECT = (0x3 << 8), - MV_SPEC_DOWNSHIFT_COUNTER = (0x3 << 12), -}; - -#define MII_MV_PAGE 22 - -#define MV_REG_FIBER_SERDES 0xf -#define MV_PAGE_FIBER_SERDES 0x1 - -struct mvsw61xx_state { - struct switch_dev dev; - struct mii_bus *bus; - int base_addr; - u16 model; - - bool registered; - bool is_indirect; - - int cpu_port0; - int cpu_port1; - - int vlan_enabled; - struct port_state { - u16 fdb; - u16 pvid; - u16 mask; - u8 qmode; - } ports[MV_PORTS]; - - struct vlan_state { - bool port_based; - - u16 mask; - u16 vid; - u32 port_mode; - u32 port_sstate; - } vlans[MV_VLANS]; - - char buf[128]; -}; - -#define get_state(_dev) container_of((_dev), struct mvsw61xx_state, dev) - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/mvswitch.c b/target/linux/generic/files/drivers/net/phy/mvswitch.c deleted file mode 100644 index 043978feb9..0000000000 --- a/target/linux/generic/files/drivers/net/phy/mvswitch.c +++ /dev/null @@ -1,444 +0,0 @@ -/* - * Marvell 88E6060 switch driver - * Copyright (c) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include "mvswitch.h" - -/* Undefine this to use trailer mode instead. - * I don't know if header mode works with all chips */ -#define HEADER_MODE 1 - -MODULE_DESCRIPTION("Marvell 88E6060 Switch driver"); -MODULE_AUTHOR("Felix Fietkau"); -MODULE_LICENSE("GPL"); - -#define MVSWITCH_MAGIC 0x88E6060 - -struct mvswitch_priv { - netdev_features_t orig_features; - u8 vlans[16]; -}; - -#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) - -static inline u16 -r16(struct phy_device *phydev, int addr, int reg) -{ - struct mii_bus *bus = phydev->mdio.bus; - - return bus->read(bus, addr, reg); -} - -static inline void -w16(struct phy_device *phydev, int addr, int reg, u16 val) -{ - struct mii_bus *bus = phydev->mdio.bus; - - bus->write(bus, addr, reg, val); -} - - -static struct sk_buff * -mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) -{ - struct mvswitch_priv *priv; - char *buf = NULL; - u16 vid; - - priv = dev->phy_ptr; - if (unlikely(!priv)) - goto error; - - if (unlikely(skb->len < 16)) - goto error; - -#ifdef HEADER_MODE - if (__vlan_hwaccel_get_tag(skb, &vid)) - goto error; - - if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) { - if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC)) - goto error_expand; - if (skb->len < 62) - skb->len = 62; - } - buf = skb_push(skb, MV_HEADER_SIZE); -#else - if (__vlan_get_tag(skb, &vid)) - goto error; - - if (unlikely((vid > 15 || !priv->vlans[vid]))) - goto error; - - if (skb->len <= 64) { - if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) - goto error_expand; - - buf = skb->data + 64; - skb->len = 64 + MV_TRAILER_SIZE; - } else { - if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) { - if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) - goto error_expand; - } - buf = skb_put(skb, 4); - } - - /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ - memmove(skb->data + 4, skb->data, 12); - skb->data += 4; - skb->len -= 4; - skb->mac_header += 4; -#endif - - if (!buf) - goto error; - - -#ifdef HEADER_MODE - /* prepend the tag */ - *((__be16 *) buf) = cpu_to_be16( - ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) | - ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M) - ); -#else - /* append the tag */ - *((__be32 *) buf) = cpu_to_be32(( - (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) | - ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S) - )); -#endif - - return skb; - -error_expand: - if (net_ratelimit()) - printk("%s: failed to expand/update skb for the switch\n", dev->name); - -error: - /* any errors? drop the packet! */ - dev_kfree_skb_any(skb); - return NULL; -} - -static void -mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) -{ - struct mvswitch_priv *priv; - unsigned char *buf; - int vlan = -1; - int i; - - priv = dev->phy_ptr; - if (WARN_ON_ONCE(!priv)) - return; - -#ifdef HEADER_MODE - buf = skb->data; - skb_pull(skb, MV_HEADER_SIZE); -#else - buf = skb->data + skb->len - MV_TRAILER_SIZE; - if (buf[0] != 0x80) - return; -#endif - - /* look for the vlan matching the incoming port */ - for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { - if ((1 << buf[1]) & priv->vlans[i]) - vlan = i; - } - - if (vlan == -1) - return; - - __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); -} - - -static int -mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val) -{ - int i = 100; - u16 r; - - do { - r = r16(pdev, addr, reg) & mask; - if (r == val) - return 0; - } while(--i > 0); - return -ETIMEDOUT; -} - -static int -mvswitch_config_init(struct phy_device *pdev) -{ - struct mvswitch_priv *priv = to_mvsw(pdev); - struct net_device *dev = pdev->attached_dev; - u8 vlmap = 0; - int i; - - if (!dev) - return -EINVAL; - - printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); - pdev->supported = ADVERTISED_100baseT_Full; - pdev->advertising = ADVERTISED_100baseT_Full; - dev->phy_ptr = priv; - pdev->irq = PHY_POLL; -#ifdef HEADER_MODE - dev->flags |= IFF_PROMISC; -#endif - - /* initialize default vlans */ - for (i = 0; i < MV_PORTS; i++) - priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i); - - /* before entering reset, disable all ports */ - for (i = 0; i < MV_PORTS; i++) - w16(pdev, MV_PORTREG(CONTROL, i), 0x00); - - msleep(2); /* wait for the status change to settle in */ - - /* put the ATU in reset */ - w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); - - i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0); - if (i < 0) { - printk("%s: Timeout waiting for the switch to reset.\n", dev->name); - return i; - } - - /* set the ATU flags */ - w16(pdev, MV_SWITCHREG(ATU_CTRL), - MV_ATUCTL_NO_LEARN | - MV_ATUCTL_ATU_1K | - MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */ - ); - - /* initialize the cpu port */ - w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT), -#ifdef HEADER_MODE - MV_PORTCTRL_HEADER | -#else - MV_PORTCTRL_RXTR | - MV_PORTCTRL_TXTR | -#endif - MV_PORTCTRL_ENABLED - ); - /* wait for the phy change to settle in */ - msleep(2); - for (i = 0; i < MV_PORTS; i++) { - u8 pvid = 0; - int j; - - vlmap = 0; - - /* look for the matching vlan */ - for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { - if (priv->vlans[j] & (1 << i)) { - vlmap = priv->vlans[j]; - pvid = j; - } - } - /* leave port unconfigured if it's not part of a vlan */ - if (!vlmap) - continue; - - /* add the cpu port to the allowed destinations list */ - vlmap |= (1 << MV_CPUPORT); - - /* take port out of its own vlan destination map */ - vlmap &= ~(1 << i); - - /* apply vlan settings */ - w16(pdev, MV_PORTREG(VLANMAP, i), - MV_PORTVLAN_PORTS(vlmap) | - MV_PORTVLAN_ID(i) - ); - - /* re-enable port */ - w16(pdev, MV_PORTREG(CONTROL, i), - MV_PORTCTRL_ENABLED - ); - } - - w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), - MV_PORTVLAN_ID(MV_CPUPORT) - ); - - /* set the port association vector */ - for (i = 0; i <= MV_PORTS; i++) { - w16(pdev, MV_PORTREG(ASSOC, i), - MV_PORTASSOC_PORTS(1 << i) - ); - } - - /* init switch control */ - w16(pdev, MV_SWITCHREG(CTRL), - MV_SWITCHCTL_MSIZE | - MV_SWITCHCTL_DROP - ); - - dev->eth_mangle_rx = mvswitch_mangle_rx; - dev->eth_mangle_tx = mvswitch_mangle_tx; - priv->orig_features = dev->features; - -#ifdef HEADER_MODE - dev->priv_flags |= IFF_NO_IP_ALIGN; - dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; -#else - dev->features |= NETIF_F_HW_VLAN_CTAG_RX; -#endif - - return 0; -} - -static int -mvswitch_read_status(struct phy_device *pdev) -{ - pdev->speed = SPEED_100; - pdev->duplex = DUPLEX_FULL; - pdev->link = 1; - - /* XXX ugly workaround: we can't force the switch - * to gracefully handle hosts moving from one port to another, - * so we have to regularly clear the ATU database */ - - /* wait for the ATU to become available */ - mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); - - /* flush the ATU */ - w16(pdev, MV_SWITCHREG(ATU_OP), - MV_ATUOP_INPROGRESS | - MV_ATUOP_FLUSH_ALL - ); - - /* wait for operation to complete */ - mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0); - - return 0; -} - -static int -mvswitch_aneg_done(struct phy_device *phydev) -{ - return 1; /* Return any positive value */ -} - -static int -mvswitch_config_aneg(struct phy_device *phydev) -{ - return 0; -} - -static void -mvswitch_detach(struct phy_device *pdev) -{ - struct mvswitch_priv *priv = to_mvsw(pdev); - struct net_device *dev = pdev->attached_dev; - - if (!dev) - return; - - dev->phy_ptr = NULL; - dev->eth_mangle_rx = NULL; - dev->eth_mangle_tx = NULL; - dev->features = priv->orig_features; - dev->priv_flags &= ~IFF_NO_IP_ALIGN; -} - -static void -mvswitch_remove(struct phy_device *pdev) -{ - struct mvswitch_priv *priv = to_mvsw(pdev); - - kfree(priv); -} - -static int -mvswitch_probe(struct phy_device *pdev) -{ - struct mvswitch_priv *priv; - - priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); - if (priv == NULL) - return -ENOMEM; - - pdev->priv = priv; - - return 0; -} - -static int -mvswitch_fixup(struct phy_device *dev) -{ - struct mii_bus *bus = dev->mdio.bus; - u16 reg; - - if (dev->mdio.addr != 0x10) - return 0; - - reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; - if (reg != MV_IDENT_VALUE) - return 0; - - dev->phy_id = MVSWITCH_MAGIC; - return 0; -} - - -static struct phy_driver mvswitch_driver = { - .name = "Marvell 88E6060", - .phy_id = MVSWITCH_MAGIC, - .phy_id_mask = 0xffffffff, - .features = PHY_BASIC_FEATURES, - .probe = &mvswitch_probe, - .remove = &mvswitch_remove, - .detach = &mvswitch_detach, - .config_init = &mvswitch_config_init, - .config_aneg = &mvswitch_config_aneg, - .aneg_done = &mvswitch_aneg_done, - .read_status = &mvswitch_read_status, -}; - -static int __init -mvswitch_init(void) -{ - phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); - return phy_driver_register(&mvswitch_driver, THIS_MODULE); -} - -static void __exit -mvswitch_exit(void) -{ - phy_driver_unregister(&mvswitch_driver); -} - -module_init(mvswitch_init); -module_exit(mvswitch_exit); diff --git a/target/linux/generic/files/drivers/net/phy/mvswitch.h b/target/linux/generic/files/drivers/net/phy/mvswitch.h deleted file mode 100644 index ab2a1a126e..0000000000 --- a/target/linux/generic/files/drivers/net/phy/mvswitch.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Marvell 88E6060 switch driver - * Copyright (c) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ -#ifndef __MVSWITCH_H -#define __MVSWITCH_H - -#define MV_HEADER_SIZE 2 -#define MV_HEADER_PORTS_M 0x001f -#define MV_HEADER_PORTS_S 0 -#define MV_HEADER_VLAN_M 0xf000 -#define MV_HEADER_VLAN_S 12 - -#define MV_TRAILER_SIZE 4 -#define MV_TRAILER_PORTS_M 0x1f -#define MV_TRAILER_PORTS_S 16 -#define MV_TRAILER_FLAGS_S 24 -#define MV_TRAILER_OVERRIDE 0x80 - - -#define MV_PORTS 5 -#define MV_WANPORT 4 -#define MV_CPUPORT 5 - -#define MV_BASE 0x10 - -#define MV_PHYPORT_BASE (MV_BASE + 0x0) -#define MV_PHYPORT(_n) (MV_PHYPORT_BASE + (_n)) -#define MV_SWITCHPORT_BASE (MV_BASE + 0x8) -#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) -#define MV_SWITCHREGS (MV_BASE + 0xf) - -enum { - MV_PHY_CONTROL = 0x00, - MV_PHY_STATUS = 0x01, - MV_PHY_IDENT0 = 0x02, - MV_PHY_IDENT1 = 0x03, - MV_PHY_ANEG = 0x04, - MV_PHY_LINK_ABILITY = 0x05, - MV_PHY_ANEG_EXPAND = 0x06, - MV_PHY_XMIT_NEXTP = 0x07, - MV_PHY_LINK_NEXTP = 0x08, - MV_PHY_CONTROL1 = 0x10, - MV_PHY_STATUS1 = 0x11, - MV_PHY_INTR_EN = 0x12, - MV_PHY_INTR_STATUS = 0x13, - MV_PHY_INTR_PORT = 0x14, - MV_PHY_RECV_COUNTER = 0x16, - MV_PHY_LED_PARALLEL = 0x16, - MV_PHY_LED_STREAM = 0x17, - MV_PHY_LED_CTRL = 0x18, - MV_PHY_LED_OVERRIDE = 0x19, - MV_PHY_VCT_CTRL = 0x1a, - MV_PHY_VCT_STATUS = 0x1b, - MV_PHY_CONTROL2 = 0x1e -}; -#define MV_PHYREG(_type, _port) MV_PHYPORT(_port), MV_PHY_##_type - -enum { - MV_PORT_STATUS = 0x00, - MV_PORT_IDENT = 0x03, - MV_PORT_CONTROL = 0x04, - MV_PORT_VLANMAP = 0x06, - MV_PORT_ASSOC = 0x0b, - MV_PORT_RXCOUNT = 0x10, - MV_PORT_TXCOUNT = 0x11, -}; -#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type - -enum { - MV_PORTCTRL_BLOCK = (1 << 0), - MV_PORTCTRL_LEARN = (2 << 0), - MV_PORTCTRL_ENABLED = (3 << 0), - MV_PORTCTRL_VLANTUN = (1 << 7), /* Enforce VLANs on packets */ - MV_PORTCTRL_RXTR = (1 << 8), /* Enable Marvell packet trailer for ingress */ - MV_PORTCTRL_HEADER = (1 << 11), /* Enable Marvell packet header mode for port */ - MV_PORTCTRL_TXTR = (1 << 14), /* Enable Marvell packet trailer for egress */ - MV_PORTCTRL_FORCEFL = (1 << 15), /* force flow control */ -}; - -#define MV_PORTVLAN_ID(_n) (((_n) & 0xf) << 12) -#define MV_PORTVLAN_PORTS(_n) ((_n) & 0x3f) - -#define MV_PORTASSOC_PORTS(_n) ((_n) & 0x1f) -#define MV_PORTASSOC_MONITOR (1 << 15) - -enum { - MV_SWITCH_MAC0 = 0x01, - MV_SWITCH_MAC1 = 0x02, - MV_SWITCH_MAC2 = 0x03, - MV_SWITCH_CTRL = 0x04, - MV_SWITCH_ATU_CTRL = 0x0a, - MV_SWITCH_ATU_OP = 0x0b, - MV_SWITCH_ATU_DATA = 0x0c, - MV_SWITCH_ATU_MAC0 = 0x0d, - MV_SWITCH_ATU_MAC1 = 0x0e, - MV_SWITCH_ATU_MAC2 = 0x0f, -}; -#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type - -enum { - MV_SWITCHCTL_EEIE = (1 << 0), /* EEPROM interrupt enable */ - MV_SWITCHCTL_PHYIE = (1 << 1), /* PHY interrupt enable */ - MV_SWITCHCTL_ATUDONE= (1 << 2), /* ATU done interrupt enable */ - MV_SWITCHCTL_ATUIE = (1 << 3), /* ATU interrupt enable */ - MV_SWITCHCTL_CTRMODE= (1 << 8), /* statistics for rx and tx errors */ - MV_SWITCHCTL_RELOAD = (1 << 9), /* reload registers from eeprom */ - MV_SWITCHCTL_MSIZE = (1 << 10), /* increase maximum frame size */ - MV_SWITCHCTL_DROP = (1 << 13), /* discard frames with excessive collisions */ -}; - -enum { -#define MV_ATUCTL_AGETIME_MIN 16 -#define MV_ATUCTL_AGETIME_MAX 4080 -#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) - MV_ATUCTL_ATU_256 = (0 << 12), - MV_ATUCTL_ATU_512 = (1 << 12), - MV_ATUCTL_ATU_1K = (2 << 12), - MV_ATUCTL_ATUMASK = (3 << 12), - MV_ATUCTL_NO_LEARN = (1 << 14), - MV_ATUCTL_RESET = (1 << 15), -}; - -enum { -#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) - - MV_ATUOP_NOOP = (0 << 12), - MV_ATUOP_FLUSH_ALL = (1 << 12), - MV_ATUOP_FLUSH_U = (2 << 12), - MV_ATUOP_LOAD_DB = (3 << 12), - MV_ATUOP_GET_NEXT = (4 << 12), - MV_ATUOP_FLUSH_DB = (5 << 12), - MV_ATUOP_FLUSH_DB_UU= (6 << 12), - - MV_ATUOP_INPROGRESS = (1 << 15), -}; - -#define MV_IDENT_MASK 0xfff0 -#define MV_IDENT_VALUE 0x0600 - -#endif diff --git a/target/linux/generic/files/drivers/net/phy/psb6970.c b/target/linux/generic/files/drivers/net/phy/psb6970.c deleted file mode 100644 index c1a381c052..0000000000 --- a/target/linux/generic/files/drivers/net/phy/psb6970.c +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Lantiq PSB6970 (Tantos) Switch driver - * - * Copyright (c) 2009,2010 Team Embedded. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation. - * - * The switch programming done in this driver follows the - * "Ethernet Traffic Separation using VLAN" Application Note as - * published by Lantiq. - */ - -#include -#include -#include -#include - -#define PSB6970_MAX_VLANS 16 -#define PSB6970_NUM_PORTS 7 -#define PSB6970_DEFAULT_PORT_CPU 6 -#define PSB6970_IS_CPU_PORT(x) ((x) > 4) - -#define PHYADDR(_reg) ((_reg >> 5) & 0xff), (_reg & 0x1f) - -/* --- Identification --- */ -#define PSB6970_CI0 0x0100 -#define PSB6970_CI0_MASK 0x000f -#define PSB6970_CI1 0x0101 -#define PSB6970_CI1_VAL 0x2599 -#define PSB6970_CI1_MASK 0xffff - -/* --- VLAN filter table --- */ -#define PSB6970_VFxL(i) ((i)*2+0x10) /* VLAN Filter Low */ -#define PSB6970_VFxL_VV (1 << 15) /* VLAN_Valid */ - -#define PSB6970_VFxH(i) ((i)*2+0x11) /* VLAN Filter High */ -#define PSB6970_VFxH_TM_SHIFT 7 /* Tagged Member */ - -/* --- Port registers --- */ -#define PSB6970_EC(p) ((p)*0x20+2) /* Extended Control */ -#define PSB6970_EC_IFNTE (1 << 1) /* Input Force No Tag Enable */ - -#define PSB6970_PBVM(p) ((p)*0x20+3) /* Port Base VLAN Map */ -#define PSB6970_PBVM_VMCE (1 << 8) -#define PSB6970_PBVM_AOVTP (1 << 9) -#define PSB6970_PBVM_VSD (1 << 10) -#define PSB6970_PBVM_VC (1 << 11) /* VID Check with VID table */ -#define PSB6970_PBVM_TBVE (1 << 13) /* Tag-Based VLAN enable */ - -#define PSB6970_DVID(p) ((p)*0x20+4) /* Default VLAN ID & Priority */ - -struct psb6970_priv { - struct switch_dev dev; - struct phy_device *phy; - u16 (*read) (struct phy_device* phydev, int reg); - void (*write) (struct phy_device* phydev, int reg, u16 val); - struct mutex reg_mutex; - - /* all fields below are cleared on reset */ - bool vlan; - u16 vlan_id[PSB6970_MAX_VLANS]; - u8 vlan_table[PSB6970_MAX_VLANS]; - u8 vlan_tagged; - u16 pvid[PSB6970_NUM_PORTS]; -}; - -#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) - -static u16 psb6970_mii_read(struct phy_device *phydev, int reg) -{ - struct mii_bus *bus = phydev->mdio.bus; - - return bus->read(bus, PHYADDR(reg)); -} - -static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) -{ - struct mii_bus *bus = phydev->mdio.bus; - - bus->write(bus, PHYADDR(reg), val); -} - -static int -psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - priv->vlan = !!val->value.i; - return 0; -} - -static int -psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - val->value.i = priv->vlan; - return 0; -} - -static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) -{ - struct psb6970_priv *priv = to_psb6970(dev); - - /* make sure no invalid PVIDs get set */ - if (vlan >= dev->vlans) - return -EINVAL; - - priv->pvid[port] = vlan; - return 0; -} - -static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) -{ - struct psb6970_priv *priv = to_psb6970(dev); - *vlan = priv->pvid[port]; - return 0; -} - -static int -psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - priv->vlan_id[val->port_vlan] = val->value.i; - return 0; -} - -static int -psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - val->value.i = priv->vlan_id[val->port_vlan]; - return 0; -} - -static struct switch_attr psb6970_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = psb6970_set_vlan, - .get = psb6970_get_vlan, - .max = 1}, -}; - -static struct switch_attr psb6970_port[] = { -}; - -static struct switch_attr psb6970_vlan[] = { - { - .type = SWITCH_TYPE_INT, - .name = "vid", - .description = "VLAN ID (0-4094)", - .set = psb6970_set_vid, - .get = psb6970_get_vid, - .max = 4094, - }, -}; - -static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - u8 ports = priv->vlan_table[val->port_vlan]; - int i; - - val->len = 0; - for (i = 0; i < PSB6970_NUM_PORTS; i++) { - struct switch_port *p; - - if (!(ports & (1 << i))) - continue; - - p = &val->value.ports[val->len++]; - p->id = i; - if (priv->vlan_tagged & (1 << i)) - p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); - else - p->flags = 0; - } - return 0; -} - -static int psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct psb6970_priv *priv = to_psb6970(dev); - u8 *vt = &priv->vlan_table[val->port_vlan]; - int i, j; - - *vt = 0; - for (i = 0; i < val->len; i++) { - struct switch_port *p = &val->value.ports[i]; - - if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) - priv->vlan_tagged |= (1 << p->id); - else { - priv->vlan_tagged &= ~(1 << p->id); - priv->pvid[p->id] = val->port_vlan; - - /* make sure that an untagged port does not - * appear in other vlans */ - for (j = 0; j < PSB6970_MAX_VLANS; j++) { - if (j == val->port_vlan) - continue; - priv->vlan_table[j] &= ~(1 << p->id); - } - } - - *vt |= 1 << p->id; - } - return 0; -} - -static int psb6970_hw_apply(struct switch_dev *dev) -{ - struct psb6970_priv *priv = to_psb6970(dev); - int i, j; - - mutex_lock(&priv->reg_mutex); - - if (priv->vlan) { - /* into the vlan translation unit */ - for (j = 0; j < PSB6970_MAX_VLANS; j++) { - u8 vp = priv->vlan_table[j]; - - if (vp) { - priv->write(priv->phy, PSB6970_VFxL(j), - PSB6970_VFxL_VV | priv->vlan_id[j]); - priv->write(priv->phy, PSB6970_VFxH(j), - ((vp & priv-> - vlan_tagged) << - PSB6970_VFxH_TM_SHIFT) | vp); - } else /* clear VLAN Valid flag for unused vlans */ - priv->write(priv->phy, PSB6970_VFxL(j), 0); - - } - } - - /* update the port destination mask registers and tag settings */ - for (i = 0; i < PSB6970_NUM_PORTS; i++) { - int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; - - if (priv->vlan) { - ec = PSB6970_EC_IFNTE; - dvid = priv->vlan_id[priv->pvid[i]]; - pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; - - if ((i << 1) & priv->vlan_tagged) - pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; - } - - priv->write(priv->phy, PSB6970_PBVM(i), pbvm); - - if (!PSB6970_IS_CPU_PORT(i)) { - priv->write(priv->phy, PSB6970_EC(i), ec); - priv->write(priv->phy, PSB6970_DVID(i), dvid); - } - } - - mutex_unlock(&priv->reg_mutex); - return 0; -} - -static int psb6970_reset_switch(struct switch_dev *dev) -{ - struct psb6970_priv *priv = to_psb6970(dev); - int i; - - mutex_lock(&priv->reg_mutex); - - memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - - offsetof(struct psb6970_priv, vlan)); - - for (i = 0; i < PSB6970_MAX_VLANS; i++) - priv->vlan_id[i] = i; - - mutex_unlock(&priv->reg_mutex); - - return psb6970_hw_apply(dev); -} - -static const struct switch_dev_ops psb6970_ops = { - .attr_global = { - .attr = psb6970_globals, - .n_attr = ARRAY_SIZE(psb6970_globals), - }, - .attr_port = { - .attr = psb6970_port, - .n_attr = ARRAY_SIZE(psb6970_port), - }, - .attr_vlan = { - .attr = psb6970_vlan, - .n_attr = ARRAY_SIZE(psb6970_vlan), - }, - .get_port_pvid = psb6970_get_pvid, - .set_port_pvid = psb6970_set_pvid, - .get_vlan_ports = psb6970_get_ports, - .set_vlan_ports = psb6970_set_ports, - .apply_config = psb6970_hw_apply, - .reset_switch = psb6970_reset_switch, -}; - -static int psb6970_config_init(struct phy_device *pdev) -{ - struct psb6970_priv *priv; - struct net_device *dev = pdev->attached_dev; - struct switch_dev *swdev; - int ret; - - priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); - if (priv == NULL) - return -ENOMEM; - - priv->phy = pdev; - - if (pdev->mdio.addr == 0) - printk(KERN_INFO "%s: psb6970 switch driver attached.\n", - pdev->attached_dev->name); - - if (pdev->mdio.addr != 0) { - kfree(priv); - return 0; - } - - pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; - - mutex_init(&priv->reg_mutex); - priv->read = psb6970_mii_read; - priv->write = psb6970_mii_write; - - pdev->priv = priv; - - swdev = &priv->dev; - swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; - swdev->ops = &psb6970_ops; - - swdev->name = "Lantiq PSB6970"; - swdev->vlans = PSB6970_MAX_VLANS; - swdev->ports = PSB6970_NUM_PORTS; - - if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { - kfree(priv); - goto done; - } - - ret = psb6970_reset_switch(&priv->dev); - if (ret) { - kfree(priv); - goto done; - } - - dev->phy_ptr = priv; - -done: - return ret; -} - -static int psb6970_read_status(struct phy_device *phydev) -{ - phydev->speed = SPEED_100; - phydev->duplex = DUPLEX_FULL; - phydev->link = 1; - - phydev->state = PHY_RUNNING; - netif_carrier_on(phydev->attached_dev); - phydev->adjust_link(phydev->attached_dev); - - return 0; -} - -static int psb6970_config_aneg(struct phy_device *phydev) -{ - return 0; -} - -static int psb6970_probe(struct phy_device *pdev) -{ - return 0; -} - -static void psb6970_remove(struct phy_device *pdev) -{ - struct psb6970_priv *priv = pdev->priv; - - if (!priv) - return; - - if (pdev->mdio.addr == 0) - unregister_switch(&priv->dev); - kfree(priv); -} - -static int psb6970_fixup(struct phy_device *dev) -{ - struct mii_bus *bus = dev->mdio.bus; - u16 reg; - - /* look for the switch on the bus */ - reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; - if (reg != PSB6970_CI1_VAL) - return 0; - - dev->phy_id = (reg << 16); - dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; - - return 0; -} - -static struct phy_driver psb6970_driver = { - .name = "Lantiq PSB6970", - .phy_id = PSB6970_CI1_VAL << 16, - .phy_id_mask = 0xffff0000, - .features = PHY_BASIC_FEATURES, - .probe = psb6970_probe, - .remove = psb6970_remove, - .config_init = &psb6970_config_init, - .config_aneg = &psb6970_config_aneg, - .read_status = &psb6970_read_status, -}; - -int __init psb6970_init(void) -{ - phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); - return phy_driver_register(&psb6970_driver, THIS_MODULE); -} - -module_init(psb6970_init); - -void __exit psb6970_exit(void) -{ - phy_driver_unregister(&psb6970_driver); -} - -module_exit(psb6970_exit); - -MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); -MODULE_AUTHOR("Ithamar R. Adema "); -MODULE_LICENSE("GPL"); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8306.c b/target/linux/generic/files/drivers/net/phy/rtl8306.c deleted file mode 100644 index 6d09c1063c..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8306.c +++ /dev/null @@ -1,1066 +0,0 @@ -/* - * rtl8306.c: RTL8306S switch driver - * - * Copyright (C) 2009 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#define DEBUG 1 - -/* Global (PHY0) */ -#define RTL8306_REG_PAGE 16 -#define RTL8306_REG_PAGE_LO (1 << 15) -#define RTL8306_REG_PAGE_HI (1 << 1) /* inverted */ - -#define RTL8306_NUM_VLANS 16 -#define RTL8306_NUM_PORTS 6 -#define RTL8306_PORT_CPU 5 -#define RTL8306_NUM_PAGES 4 -#define RTL8306_NUM_REGS 32 - -#define RTL_NAME_S "RTL8306S" -#define RTL_NAME_SD "RTL8306SD" -#define RTL_NAME_SDM "RTL8306SDM" -#define RTL_NAME_UNKNOWN "RTL8306(unknown)" - -#define RTL8306_MAGIC 0x8306 - -static LIST_HEAD(phydevs); - -struct rtl_priv { - struct list_head list; - struct switch_dev dev; - int page; - int type; - int do_cpu; - struct mii_bus *bus; - char hwname[sizeof(RTL_NAME_UNKNOWN)]; - bool fixup; -}; - -struct rtl_phyregs { - int nway; - int speed; - int duplex; -}; - -#define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) - -enum { - RTL_TYPE_S, - RTL_TYPE_SD, - RTL_TYPE_SDM, -}; - -struct rtl_reg { - int page; - int phy; - int reg; - int bits; - int shift; - int inverted; -}; - -#define RTL_VLAN_REGOFS(name) \ - (RTL_REG_VLAN1_##name - RTL_REG_VLAN0_##name) - -#define RTL_PORT_REGOFS(name) \ - (RTL_REG_PORT1_##name - RTL_REG_PORT0_##name) - -#define RTL_PORT_REG(id, reg) \ - (RTL_REG_PORT0_##reg + (id * RTL_PORT_REGOFS(reg))) - -#define RTL_VLAN_REG(id, reg) \ - (RTL_REG_VLAN0_##reg + (id * RTL_VLAN_REGOFS(reg))) - -#define RTL_GLOBAL_REGATTR(reg) \ - .id = RTL_REG_##reg, \ - .type = SWITCH_TYPE_INT, \ - .ofs = 0, \ - .set = rtl_attr_set_int, \ - .get = rtl_attr_get_int - -#define RTL_PORT_REGATTR(reg) \ - .id = RTL_REG_PORT0_##reg, \ - .type = SWITCH_TYPE_INT, \ - .ofs = RTL_PORT_REGOFS(reg), \ - .set = rtl_attr_set_port_int, \ - .get = rtl_attr_get_port_int - -#define RTL_VLAN_REGATTR(reg) \ - .id = RTL_REG_VLAN0_##reg, \ - .type = SWITCH_TYPE_INT, \ - .ofs = RTL_VLAN_REGOFS(reg), \ - .set = rtl_attr_set_vlan_int, \ - .get = rtl_attr_get_vlan_int - -enum rtl_regidx { - RTL_REG_CHIPID, - RTL_REG_CHIPVER, - RTL_REG_CHIPTYPE, - RTL_REG_CPUPORT, - - RTL_REG_EN_CPUPORT, - RTL_REG_EN_TAG_OUT, - RTL_REG_EN_TAG_CLR, - RTL_REG_EN_TAG_IN, - RTL_REG_TRAP_CPU, - RTL_REG_CPU_LINKUP, - RTL_REG_TRUNK_PORTSEL, - RTL_REG_EN_TRUNK, - RTL_REG_RESET, - - RTL_REG_VLAN_ENABLE, - RTL_REG_VLAN_FILTER, - RTL_REG_VLAN_TAG_ONLY, - RTL_REG_VLAN_TAG_AWARE, -#define RTL_VLAN_ENUM(id) \ - RTL_REG_VLAN##id##_VID, \ - RTL_REG_VLAN##id##_PORTMASK - RTL_VLAN_ENUM(0), - RTL_VLAN_ENUM(1), - RTL_VLAN_ENUM(2), - RTL_VLAN_ENUM(3), - RTL_VLAN_ENUM(4), - RTL_VLAN_ENUM(5), - RTL_VLAN_ENUM(6), - RTL_VLAN_ENUM(7), - RTL_VLAN_ENUM(8), - RTL_VLAN_ENUM(9), - RTL_VLAN_ENUM(10), - RTL_VLAN_ENUM(11), - RTL_VLAN_ENUM(12), - RTL_VLAN_ENUM(13), - RTL_VLAN_ENUM(14), - RTL_VLAN_ENUM(15), -#define RTL_PORT_ENUM(id) \ - RTL_REG_PORT##id##_PVID, \ - RTL_REG_PORT##id##_NULL_VID_REPLACE, \ - RTL_REG_PORT##id##_NON_PVID_DISCARD, \ - RTL_REG_PORT##id##_VID_INSERT, \ - RTL_REG_PORT##id##_TAG_INSERT, \ - RTL_REG_PORT##id##_LINK, \ - RTL_REG_PORT##id##_SPEED, \ - RTL_REG_PORT##id##_NWAY, \ - RTL_REG_PORT##id##_NRESTART, \ - RTL_REG_PORT##id##_DUPLEX, \ - RTL_REG_PORT##id##_RXEN, \ - RTL_REG_PORT##id##_TXEN - RTL_PORT_ENUM(0), - RTL_PORT_ENUM(1), - RTL_PORT_ENUM(2), - RTL_PORT_ENUM(3), - RTL_PORT_ENUM(4), - RTL_PORT_ENUM(5), -}; - -static const struct rtl_reg rtl_regs[] = { - [RTL_REG_CHIPID] = { 0, 4, 30, 16, 0, 0 }, - [RTL_REG_CHIPVER] = { 0, 4, 31, 8, 0, 0 }, - [RTL_REG_CHIPTYPE] = { 0, 4, 31, 2, 8, 0 }, - - /* CPU port number */ - [RTL_REG_CPUPORT] = { 2, 4, 21, 3, 0, 0 }, - /* Enable CPU port function */ - [RTL_REG_EN_CPUPORT] = { 3, 2, 21, 1, 15, 1 }, - /* Enable CPU port tag insertion */ - [RTL_REG_EN_TAG_OUT] = { 3, 2, 21, 1, 12, 0 }, - /* Enable CPU port tag removal */ - [RTL_REG_EN_TAG_CLR] = { 3, 2, 21, 1, 11, 0 }, - /* Enable CPU port tag checking */ - [RTL_REG_EN_TAG_IN] = { 0, 4, 21, 1, 7, 0 }, - [RTL_REG_EN_TRUNK] = { 0, 0, 19, 1, 11, 1 }, - [RTL_REG_TRUNK_PORTSEL] = { 0, 0, 16, 1, 6, 1 }, - [RTL_REG_RESET] = { 0, 0, 16, 1, 12, 0 }, - - [RTL_REG_TRAP_CPU] = { 3, 2, 22, 1, 6, 0 }, - [RTL_REG_CPU_LINKUP] = { 0, 6, 22, 1, 15, 0 }, - - [RTL_REG_VLAN_TAG_ONLY] = { 0, 0, 16, 1, 8, 1 }, - [RTL_REG_VLAN_FILTER] = { 0, 0, 16, 1, 9, 1 }, - [RTL_REG_VLAN_TAG_AWARE] = { 0, 0, 16, 1, 10, 1 }, - [RTL_REG_VLAN_ENABLE] = { 0, 0, 18, 1, 8, 1 }, - -#define RTL_VLAN_REGS(id, phy, page, regofs) \ - [RTL_REG_VLAN##id##_VID] = { page, phy, 25 + regofs, 12, 0, 0 }, \ - [RTL_REG_VLAN##id##_PORTMASK] = { page, phy, 24 + regofs, 6, 0, 0 } - RTL_VLAN_REGS( 0, 0, 0, 0), - RTL_VLAN_REGS( 1, 1, 0, 0), - RTL_VLAN_REGS( 2, 2, 0, 0), - RTL_VLAN_REGS( 3, 3, 0, 0), - RTL_VLAN_REGS( 4, 4, 0, 0), - RTL_VLAN_REGS( 5, 0, 1, 2), - RTL_VLAN_REGS( 6, 1, 1, 2), - RTL_VLAN_REGS( 7, 2, 1, 2), - RTL_VLAN_REGS( 8, 3, 1, 2), - RTL_VLAN_REGS( 9, 4, 1, 2), - RTL_VLAN_REGS(10, 0, 1, 4), - RTL_VLAN_REGS(11, 1, 1, 4), - RTL_VLAN_REGS(12, 2, 1, 4), - RTL_VLAN_REGS(13, 3, 1, 4), - RTL_VLAN_REGS(14, 4, 1, 4), - RTL_VLAN_REGS(15, 0, 1, 6), - -#define REG_PORT_SETTING(port, phy) \ - [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ - [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ - [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ - [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ - [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ - [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ - [RTL_REG_PORT##port##_LINK] = { 0, phy, 1, 1, 2, 0 }, \ - [RTL_REG_PORT##port##_NULL_VID_REPLACE] = { 0, phy, 22, 1, 12, 0 }, \ - [RTL_REG_PORT##port##_NON_PVID_DISCARD] = { 0, phy, 22, 1, 11, 0 }, \ - [RTL_REG_PORT##port##_VID_INSERT] = { 0, phy, 22, 2, 9, 0 }, \ - [RTL_REG_PORT##port##_TAG_INSERT] = { 0, phy, 22, 2, 0, 0 } - - REG_PORT_SETTING(0, 0), - REG_PORT_SETTING(1, 1), - REG_PORT_SETTING(2, 2), - REG_PORT_SETTING(3, 3), - REG_PORT_SETTING(4, 4), - REG_PORT_SETTING(5, 6), - -#define REG_PORT_PVID(phy, page, regofs) \ - { page, phy, 24 + regofs, 4, 12, 0 } - [RTL_REG_PORT0_PVID] = REG_PORT_PVID(0, 0, 0), - [RTL_REG_PORT1_PVID] = REG_PORT_PVID(1, 0, 0), - [RTL_REG_PORT2_PVID] = REG_PORT_PVID(2, 0, 0), - [RTL_REG_PORT3_PVID] = REG_PORT_PVID(3, 0, 0), - [RTL_REG_PORT4_PVID] = REG_PORT_PVID(4, 0, 0), - [RTL_REG_PORT5_PVID] = REG_PORT_PVID(0, 1, 2), -}; - - -static inline void -rtl_set_page(struct rtl_priv *priv, unsigned int page) -{ - struct mii_bus *bus = priv->bus; - u16 pgsel; - - if (priv->fixup) - return; - - if (priv->page == page) - return; - - BUG_ON(page > RTL8306_NUM_PAGES); - pgsel = bus->read(bus, 0, RTL8306_REG_PAGE); - pgsel &= ~(RTL8306_REG_PAGE_LO | RTL8306_REG_PAGE_HI); - if (page & (1 << 0)) - pgsel |= RTL8306_REG_PAGE_LO; - if (!(page & (1 << 1))) /* bit is inverted */ - pgsel |= RTL8306_REG_PAGE_HI; - bus->write(bus, 0, RTL8306_REG_PAGE, pgsel); -} - -static inline int -rtl_w16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 val) -{ - struct rtl_priv *priv = to_rtl(dev); - struct mii_bus *bus = priv->bus; - - rtl_set_page(priv, page); - bus->write(bus, phy, reg, val); - bus->read(bus, phy, reg); /* flush */ - return 0; -} - -static inline int -rtl_r16(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg) -{ - struct rtl_priv *priv = to_rtl(dev); - struct mii_bus *bus = priv->bus; - - rtl_set_page(priv, page); - return bus->read(bus, phy, reg); -} - -static inline u16 -rtl_rmw(struct switch_dev *dev, unsigned int page, unsigned int phy, unsigned int reg, u16 mask, u16 val) -{ - struct rtl_priv *priv = to_rtl(dev); - struct mii_bus *bus = priv->bus; - u16 r; - - rtl_set_page(priv, page); - r = bus->read(bus, phy, reg); - r &= ~mask; - r |= val; - bus->write(bus, phy, reg, r); - return bus->read(bus, phy, reg); /* flush */ -} - - -static inline int -rtl_get(struct switch_dev *dev, enum rtl_regidx s) -{ - const struct rtl_reg *r = &rtl_regs[s]; - u16 val; - - BUG_ON(s >= ARRAY_SIZE(rtl_regs)); - if (r->bits == 0) /* unimplemented */ - return 0; - - val = rtl_r16(dev, r->page, r->phy, r->reg); - - if (r->shift > 0) - val >>= r->shift; - - if (r->inverted) - val = ~val; - - val &= (1 << r->bits) - 1; - - return val; -} - -static int -rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) -{ - const struct rtl_reg *r = &rtl_regs[s]; - u16 mask = 0xffff; - - BUG_ON(s >= ARRAY_SIZE(rtl_regs)); - - if (r->bits == 0) /* unimplemented */ - return 0; - - if (r->shift > 0) - val <<= r->shift; - - if (r->inverted) - val = ~val; - - if (r->bits != 16) { - mask = (1 << r->bits) - 1; - mask <<= r->shift; - } - val &= mask; - return rtl_rmw(dev, r->page, r->phy, r->reg, mask, val); -} - -static void -rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) -{ - regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); - regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); - regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); -} - -static void -rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) -{ - rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); - rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); - rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); -} - -static void -rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) -{ - rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); - rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); - - if ((port >= 5) || !enabled) - return; - - /* restart autonegotiation if enabled */ - rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); -} - -static int -rtl_hw_apply(struct switch_dev *dev) -{ - int i; - int trunk_en, trunk_psel; - struct rtl_phyregs port5; - - rtl_phy_save(dev, 5, &port5); - - /* disable rx/tx from PHYs */ - for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { - rtl_port_set_enable(dev, i, 0); - } - - /* save trunking status */ - trunk_en = rtl_get(dev, RTL_REG_EN_TRUNK); - trunk_psel = rtl_get(dev, RTL_REG_TRUNK_PORTSEL); - - /* trunk port 3 and 4 - * XXX: Big WTF, but RealTek seems to do it */ - rtl_set(dev, RTL_REG_EN_TRUNK, 1); - rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 1); - - /* execute the software reset */ - rtl_set(dev, RTL_REG_RESET, 1); - - /* wait for the reset to complete, - * but don't wait for too long */ - for (i = 0; i < 10; i++) { - if (rtl_get(dev, RTL_REG_RESET) == 0) - break; - - msleep(1); - } - - /* enable rx/tx from PHYs */ - for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { - rtl_port_set_enable(dev, i, 1); - } - - /* restore trunking settings */ - rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); - rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); - rtl_phy_restore(dev, 5, &port5); - - rtl_set(dev, RTL_REG_CPU_LINKUP, 1); - - return 0; -} - -static void -rtl_hw_init(struct switch_dev *dev) -{ - struct rtl_priv *priv = to_rtl(dev); - int cpu_mask = 1 << dev->cpu_port; - int i; - - rtl_set(dev, RTL_REG_VLAN_ENABLE, 0); - rtl_set(dev, RTL_REG_VLAN_FILTER, 0); - rtl_set(dev, RTL_REG_EN_TRUNK, 0); - rtl_set(dev, RTL_REG_TRUNK_PORTSEL, 0); - - /* initialize cpu port settings */ - if (priv->do_cpu) { - rtl_set(dev, RTL_REG_CPUPORT, dev->cpu_port); - rtl_set(dev, RTL_REG_EN_CPUPORT, 1); - } else { - rtl_set(dev, RTL_REG_CPUPORT, 7); - rtl_set(dev, RTL_REG_EN_CPUPORT, 0); - } - rtl_set(dev, RTL_REG_EN_TAG_OUT, 0); - rtl_set(dev, RTL_REG_EN_TAG_IN, 0); - rtl_set(dev, RTL_REG_EN_TAG_CLR, 0); - - /* reset all vlans */ - for (i = 0; i < RTL8306_NUM_VLANS; i++) { - rtl_set(dev, RTL_VLAN_REG(i, VID), i); - rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), 0); - } - - /* default to port isolation */ - for (i = 0; i < RTL8306_NUM_PORTS; i++) { - unsigned long mask; - - if ((1 << i) == cpu_mask) - mask = ((1 << RTL8306_NUM_PORTS) - 1) & ~cpu_mask; /* all bits set */ - else - mask = cpu_mask | (1 << i); - - rtl_set(dev, RTL_VLAN_REG(i, PORTMASK), mask); - rtl_set(dev, RTL_PORT_REG(i, PVID), i); - rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); - rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), 1); - rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), 3); - } - rtl_hw_apply(dev); -} - -#ifdef DEBUG -static int -rtl_set_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct rtl_priv *priv = to_rtl(dev); - priv->do_cpu = val->value.i; - rtl_hw_init(dev); - return 0; -} - -static int -rtl_get_use_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct rtl_priv *priv = to_rtl(dev); - val->value.i = priv->do_cpu; - return 0; -} - -static int -rtl_set_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - dev->cpu_port = val->value.i; - rtl_hw_init(dev); - return 0; -} - -static int -rtl_get_cpuport(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - val->value.i = dev->cpu_port; - return 0; -} -#endif - -static int -rtl_reset(struct switch_dev *dev) -{ - rtl_hw_init(dev); - return 0; -} - -static int -rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - int idx = attr->id + (val->port_vlan * attr->ofs); - struct rtl_phyregs port; - - if (attr->id >= ARRAY_SIZE(rtl_regs)) - return -EINVAL; - - if ((attr->max > 0) && (val->value.i > attr->max)) - return -EINVAL; - - /* access to phy register 22 on port 4/5 - * needs phy status save/restore */ - if ((val->port_vlan > 3) && - (rtl_regs[idx].reg == 22) && - (rtl_regs[idx].page == 0)) { - - rtl_phy_save(dev, val->port_vlan, &port); - rtl_set(dev, idx, val->value.i); - rtl_phy_restore(dev, val->port_vlan, &port); - } else { - rtl_set(dev, idx, val->value.i); - } - - return 0; -} - -static int -rtl_attr_get_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - int idx = attr->id + (val->port_vlan * attr->ofs); - - if (idx >= ARRAY_SIZE(rtl_regs)) - return -EINVAL; - - val->value.i = rtl_get(dev, idx); - return 0; -} - -static int -rtl_attr_set_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - if (val->port_vlan >= RTL8306_NUM_PORTS) - return -EINVAL; - - return rtl_attr_set_int(dev, attr, val); -} - -static int -rtl_attr_get_port_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - if (val->port_vlan >= RTL8306_NUM_PORTS) - return -EINVAL; - return rtl_attr_get_int(dev, attr, val); -} - -static int -rtl_get_port_link(struct switch_dev *dev, int port, struct switch_port_link *link) -{ - if (port >= RTL8306_NUM_PORTS) - return -EINVAL; - - /* in case the link changes from down to up, the register is only updated on read */ - link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); - if (!link->link) - link->link = rtl_get(dev, RTL_PORT_REG(port, LINK)); - - if (!link->link) - return 0; - - link->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); - link->aneg = rtl_get(dev, RTL_PORT_REG(port, NWAY)); - - if (rtl_get(dev, RTL_PORT_REG(port, SPEED))) - link->speed = SWITCH_PORT_SPEED_100; - else - link->speed = SWITCH_PORT_SPEED_10; - - return 0; -} - -static int -rtl_attr_set_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - if (val->port_vlan >= dev->vlans) - return -EINVAL; - - return rtl_attr_set_int(dev, attr, val); -} - -static int -rtl_attr_get_vlan_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - if (val->port_vlan >= dev->vlans) - return -EINVAL; - - return rtl_attr_get_int(dev, attr, val); -} - -static int -rtl_get_ports(struct switch_dev *dev, struct switch_val *val) -{ - unsigned int i, mask; - - mask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); - for (i = 0; i < RTL8306_NUM_PORTS; i++) { - struct switch_port *port; - - if (!(mask & (1 << i))) - continue; - - port = &val->value.ports[val->len]; - port->id = i; - if (rtl_get(dev, RTL_PORT_REG(i, TAG_INSERT)) == 2 || i == dev->cpu_port) - port->flags = (1 << SWITCH_PORT_FLAG_TAGGED); - val->len++; - } - - return 0; -} - -static int -rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - struct rtl_priv *priv = to_rtl(dev); - struct rtl_phyregs port; - int en = val->value.i; - int i; - - rtl_set(dev, RTL_REG_EN_TAG_OUT, en && priv->do_cpu); - rtl_set(dev, RTL_REG_EN_TAG_IN, en && priv->do_cpu); - rtl_set(dev, RTL_REG_EN_TAG_CLR, en && priv->do_cpu); - rtl_set(dev, RTL_REG_VLAN_TAG_AWARE, en); - if (en) - rtl_set(dev, RTL_REG_VLAN_FILTER, en); - - for (i = 0; i < RTL8306_NUM_PORTS; i++) { - if (i > 3) - rtl_phy_save(dev, val->port_vlan, &port); - rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); - rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); - rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); - if (i > 3) - rtl_phy_restore(dev, val->port_vlan, &port); - } - rtl_set(dev, RTL_REG_VLAN_ENABLE, en); - - return 0; -} - -static int -rtl_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) -{ - val->value.i = rtl_get(dev, RTL_REG_VLAN_ENABLE); - return 0; -} - -static int -rtl_set_ports(struct switch_dev *dev, struct switch_val *val) -{ - unsigned int mask = 0; - unsigned int oldmask; - int i; - - for(i = 0; i < val->len; i++) - { - struct switch_port *port = &val->value.ports[i]; - bool tagged = false; - - mask |= (1 << port->id); - - if (port->id == dev->cpu_port) - continue; - - if ((i == dev->cpu_port) || - (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))) - tagged = true; - - /* fix up PVIDs for added ports */ - if (!tagged) - rtl_set(dev, RTL_PORT_REG(port->id, PVID), val->port_vlan); - - rtl_set(dev, RTL_PORT_REG(port->id, NON_PVID_DISCARD), (tagged ? 0 : 1)); - rtl_set(dev, RTL_PORT_REG(port->id, VID_INSERT), (tagged ? 0 : 1)); - rtl_set(dev, RTL_PORT_REG(port->id, TAG_INSERT), (tagged ? 2 : 1)); - } - - oldmask = rtl_get(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK)); - rtl_set(dev, RTL_VLAN_REG(val->port_vlan, PORTMASK), mask); - - /* fix up PVIDs for removed ports, default to last vlan */ - oldmask &= ~mask; - for (i = 0; i < RTL8306_NUM_PORTS; i++) { - if (!(oldmask & (1 << i))) - continue; - - if (i == dev->cpu_port) - continue; - - if (rtl_get(dev, RTL_PORT_REG(i, PVID)) == val->port_vlan) - rtl_set(dev, RTL_PORT_REG(i, PVID), dev->vlans - 1); - } - - return 0; -} - -static struct switch_attr rtl_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .max = 1, - .set = rtl_set_vlan, - .get = rtl_get_vlan, - }, - { - RTL_GLOBAL_REGATTR(EN_TRUNK), - .name = "trunk", - .description = "Enable port trunking", - .max = 1, - }, - { - RTL_GLOBAL_REGATTR(TRUNK_PORTSEL), - .name = "trunk_sel", - .description = "Select ports for trunking (0: 0,1 - 1: 3,4)", - .max = 1, - }, -#ifdef DEBUG - { - RTL_GLOBAL_REGATTR(VLAN_FILTER), - .name = "vlan_filter", - .description = "Filter incoming packets for allowed VLANS", - .max = 1, - }, - { - .type = SWITCH_TYPE_INT, - .name = "cpuport", - .description = "CPU Port", - .set = rtl_set_cpuport, - .get = rtl_get_cpuport, - .max = RTL8306_NUM_PORTS, - }, - { - .type = SWITCH_TYPE_INT, - .name = "use_cpuport", - .description = "CPU Port handling flag", - .set = rtl_set_use_cpuport, - .get = rtl_get_use_cpuport, - .max = RTL8306_NUM_PORTS, - }, - { - RTL_GLOBAL_REGATTR(TRAP_CPU), - .name = "trap_cpu", - .description = "VLAN trap to CPU", - .max = 1, - }, - { - RTL_GLOBAL_REGATTR(VLAN_TAG_AWARE), - .name = "vlan_tag_aware", - .description = "Enable VLAN tag awareness", - .max = 1, - }, - { - RTL_GLOBAL_REGATTR(VLAN_TAG_ONLY), - .name = "tag_only", - .description = "Only accept tagged packets", - .max = 1, - }, -#endif -}; -static struct switch_attr rtl_port[] = { - { - RTL_PORT_REGATTR(PVID), - .name = "pvid", - .description = "Port VLAN ID", - .max = RTL8306_NUM_VLANS - 1, - }, -#ifdef DEBUG - { - RTL_PORT_REGATTR(NULL_VID_REPLACE), - .name = "null_vid", - .description = "NULL VID gets replaced by port default vid", - .max = 1, - }, - { - RTL_PORT_REGATTR(NON_PVID_DISCARD), - .name = "non_pvid_discard", - .description = "discard packets with VID != PVID", - .max = 1, - }, - { - RTL_PORT_REGATTR(VID_INSERT), - .name = "vid_insert_remove", - .description = "how should the switch insert and remove vids ?", - .max = 3, - }, - { - RTL_PORT_REGATTR(TAG_INSERT), - .name = "tag_insert", - .description = "tag insertion handling", - .max = 3, - }, -#endif -}; - -static struct switch_attr rtl_vlan[] = { - { - RTL_VLAN_REGATTR(VID), - .name = "vid", - .description = "VLAN ID (1-4095)", - .max = 4095, - }, -}; - -static const struct switch_dev_ops rtl8306_ops = { - .attr_global = { - .attr = rtl_globals, - .n_attr = ARRAY_SIZE(rtl_globals), - }, - .attr_port = { - .attr = rtl_port, - .n_attr = ARRAY_SIZE(rtl_port), - }, - .attr_vlan = { - .attr = rtl_vlan, - .n_attr = ARRAY_SIZE(rtl_vlan), - }, - - .get_vlan_ports = rtl_get_ports, - .set_vlan_ports = rtl_set_ports, - .apply_config = rtl_hw_apply, - .reset_switch = rtl_reset, - .get_port_link = rtl_get_port_link, -}; - -static int -rtl8306_config_init(struct phy_device *pdev) -{ - struct net_device *netdev = pdev->attached_dev; - struct rtl_priv *priv = pdev->priv; - struct switch_dev *dev = &priv->dev; - struct switch_val val; - unsigned int chipid, chipver, chiptype; - int err; - - /* Only init the switch for the primary PHY */ - if (pdev->mdio.addr != 0) - return 0; - - val.value.i = 1; - priv->dev.cpu_port = RTL8306_PORT_CPU; - priv->dev.ports = RTL8306_NUM_PORTS; - priv->dev.vlans = RTL8306_NUM_VLANS; - priv->dev.ops = &rtl8306_ops; - priv->do_cpu = 0; - priv->page = -1; - priv->bus = pdev->mdio.bus; - - chipid = rtl_get(dev, RTL_REG_CHIPID); - chipver = rtl_get(dev, RTL_REG_CHIPVER); - chiptype = rtl_get(dev, RTL_REG_CHIPTYPE); - switch(chiptype) { - case 0: - case 2: - strncpy(priv->hwname, RTL_NAME_S, sizeof(priv->hwname)); - priv->type = RTL_TYPE_S; - break; - case 1: - strncpy(priv->hwname, RTL_NAME_SD, sizeof(priv->hwname)); - priv->type = RTL_TYPE_SD; - break; - case 3: - strncpy(priv->hwname, RTL_NAME_SDM, sizeof(priv->hwname)); - priv->type = RTL_TYPE_SDM; - break; - default: - strncpy(priv->hwname, RTL_NAME_UNKNOWN, sizeof(priv->hwname)); - break; - } - - dev->name = priv->hwname; - rtl_hw_init(dev); - - printk(KERN_INFO "Registering %s switch with Chip ID: 0x%04x, version: 0x%04x\n", priv->hwname, chipid, chipver); - - err = register_switch(dev, netdev); - if (err < 0) { - kfree(priv); - return err; - } - - return 0; -} - - -static int -rtl8306_fixup(struct phy_device *pdev) -{ - struct rtl_priv priv; - u16 chipid; - - /* Attach to primary LAN port and WAN port */ - if (pdev->mdio.addr != 0 && pdev->mdio.addr != 4) - return 0; - - memset(&priv, 0, sizeof(priv)); - priv.fixup = true; - priv.page = -1; - priv.bus = pdev->mdio.bus; - chipid = rtl_get(&priv.dev, RTL_REG_CHIPID); - if (chipid == 0x5988) - pdev->phy_id = RTL8306_MAGIC; - - return 0; -} - -static int -rtl8306_probe(struct phy_device *pdev) -{ - struct rtl_priv *priv; - - list_for_each_entry(priv, &phydevs, list) { - /* - * share one rtl_priv instance between virtual phy - * devices on the same bus - */ - if (priv->bus == pdev->mdio.bus) - goto found; - } - priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->bus = pdev->mdio.bus; - -found: - pdev->priv = priv; - return 0; -} - -static void -rtl8306_remove(struct phy_device *pdev) -{ - struct rtl_priv *priv = pdev->priv; - unregister_switch(&priv->dev); - kfree(priv); -} - -static int -rtl8306_config_aneg(struct phy_device *pdev) -{ - struct rtl_priv *priv = pdev->priv; - - /* Only for WAN */ - if (pdev->mdio.addr == 0) - return 0; - - /* Restart autonegotiation */ - rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); - rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); - - return 0; -} - -static int -rtl8306_read_status(struct phy_device *pdev) -{ - struct rtl_priv *priv = pdev->priv; - struct switch_dev *dev = &priv->dev; - - if (pdev->mdio.addr == 4) { - /* WAN */ - pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; - pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; - pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); - } else { - /* LAN */ - pdev->speed = SPEED_100; - pdev->duplex = DUPLEX_FULL; - pdev->link = 1; - } - - /* - * Bypass generic PHY status read, - * it doesn't work with this switch - */ - if (pdev->link) { - pdev->state = PHY_RUNNING; - netif_carrier_on(pdev->attached_dev); - pdev->adjust_link(pdev->attached_dev); - } else { - pdev->state = PHY_NOLINK; - netif_carrier_off(pdev->attached_dev); - pdev->adjust_link(pdev->attached_dev); - } - - return 0; -} - - -static struct phy_driver rtl8306_driver = { - .name = "Realtek RTL8306S", -#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,13,0)) - .flags = PHY_HAS_MAGICANEG, -#endif - .phy_id = RTL8306_MAGIC, - .phy_id_mask = 0xffffffff, - .features = PHY_BASIC_FEATURES, - .probe = &rtl8306_probe, - .remove = &rtl8306_remove, - .config_init = &rtl8306_config_init, - .config_aneg = &rtl8306_config_aneg, - .read_status = &rtl8306_read_status, -}; - - -static int __init -rtl_init(void) -{ - phy_register_fixup_for_id(PHY_ANY_ID, rtl8306_fixup); - return phy_driver_register(&rtl8306_driver, THIS_MODULE); -} - -static void __exit -rtl_exit(void) -{ - phy_driver_unregister(&rtl8306_driver); -} - -module_init(rtl_init); -module_exit(rtl_exit); -MODULE_LICENSE("GPL"); - diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c deleted file mode 100644 index c0cb680e23..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c +++ /dev/null @@ -1,1628 +0,0 @@ -/* - * Realtek RTL8366 SMI interface driver - * - * Copyright (C) 2009-2010 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef CONFIG_RTL8366_SMI_DEBUG_FS -#include -#endif - -#include "rtl8366_smi.h" - -#define RTL8366_SMI_ACK_RETRY_COUNT 5 - -#define RTL8366_SMI_HW_STOP_DELAY 25 /* msecs */ -#define RTL8366_SMI_HW_START_DELAY 100 /* msecs */ - -static inline void rtl8366_smi_clk_delay(struct rtl8366_smi *smi) -{ - ndelay(smi->clk_delay); -} - -static void rtl8366_smi_start(struct rtl8366_smi *smi) -{ - unsigned int sda = smi->gpio_sda; - unsigned int sck = smi->gpio_sck; - - /* - * Set GPIO pins to output mode, with initial state: - * SCK = 0, SDA = 1 - */ - gpio_direction_output(sck, 0); - gpio_direction_output(sda, 1); - rtl8366_smi_clk_delay(smi); - - /* CLK 1: 0 -> 1, 1 -> 0 */ - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 0); - rtl8366_smi_clk_delay(smi); - - /* CLK 2: */ - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sda, 0); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 0); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sda, 1); -} - -static void rtl8366_smi_stop(struct rtl8366_smi *smi) -{ - unsigned int sda = smi->gpio_sda; - unsigned int sck = smi->gpio_sck; - - rtl8366_smi_clk_delay(smi); - gpio_set_value(sda, 0); - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sda, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 0); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 1); - - /* add a click */ - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 0); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 1); - - /* set GPIO pins to input mode */ - gpio_direction_input(sda); - gpio_direction_input(sck); -} - -static void rtl8366_smi_write_bits(struct rtl8366_smi *smi, u32 data, u32 len) -{ - unsigned int sda = smi->gpio_sda; - unsigned int sck = smi->gpio_sck; - - for (; len > 0; len--) { - rtl8366_smi_clk_delay(smi); - - /* prepare data */ - gpio_set_value(sda, !!(data & ( 1 << (len - 1)))); - rtl8366_smi_clk_delay(smi); - - /* clocking */ - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - gpio_set_value(sck, 0); - } -} - -static void rtl8366_smi_read_bits(struct rtl8366_smi *smi, u32 len, u32 *data) -{ - unsigned int sda = smi->gpio_sda; - unsigned int sck = smi->gpio_sck; - - gpio_direction_input(sda); - - for (*data = 0; len > 0; len--) { - u32 u; - - rtl8366_smi_clk_delay(smi); - - /* clocking */ - gpio_set_value(sck, 1); - rtl8366_smi_clk_delay(smi); - u = !!gpio_get_value(sda); - gpio_set_value(sck, 0); - - *data |= (u << (len - 1)); - } - - gpio_direction_output(sda, 0); -} - -static int rtl8366_smi_wait_for_ack(struct rtl8366_smi *smi) -{ - int retry_cnt; - - retry_cnt = 0; - do { - u32 ack; - - rtl8366_smi_read_bits(smi, 1, &ack); - if (ack == 0) - break; - - if (++retry_cnt > RTL8366_SMI_ACK_RETRY_COUNT) { - dev_err(smi->parent, "ACK timeout\n"); - return -ETIMEDOUT; - } - } while (1); - - return 0; -} - -static int rtl8366_smi_write_byte(struct rtl8366_smi *smi, u8 data) -{ - rtl8366_smi_write_bits(smi, data, 8); - return rtl8366_smi_wait_for_ack(smi); -} - -static int rtl8366_smi_write_byte_noack(struct rtl8366_smi *smi, u8 data) -{ - rtl8366_smi_write_bits(smi, data, 8); - return 0; -} - -static int rtl8366_smi_read_byte0(struct rtl8366_smi *smi, u8 *data) -{ - u32 t; - - /* read data */ - rtl8366_smi_read_bits(smi, 8, &t); - *data = (t & 0xff); - - /* send an ACK */ - rtl8366_smi_write_bits(smi, 0x00, 1); - - return 0; -} - -static int rtl8366_smi_read_byte1(struct rtl8366_smi *smi, u8 *data) -{ - u32 t; - - /* read data */ - rtl8366_smi_read_bits(smi, 8, &t); - *data = (t & 0xff); - - /* send an ACK */ - rtl8366_smi_write_bits(smi, 0x01, 1); - - return 0; -} - -static int __rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) -{ - unsigned long flags; - u8 lo = 0; - u8 hi = 0; - int ret; - - spin_lock_irqsave(&smi->lock, flags); - - rtl8366_smi_start(smi); - - /* send READ command */ - ret = rtl8366_smi_write_byte(smi, smi->cmd_read); - if (ret) - goto out; - - /* set ADDR[7:0] */ - ret = rtl8366_smi_write_byte(smi, addr & 0xff); - if (ret) - goto out; - - /* set ADDR[15:8] */ - ret = rtl8366_smi_write_byte(smi, addr >> 8); - if (ret) - goto out; - - /* read DATA[7:0] */ - rtl8366_smi_read_byte0(smi, &lo); - /* read DATA[15:8] */ - rtl8366_smi_read_byte1(smi, &hi); - - *data = ((u32) lo) | (((u32) hi) << 8); - - ret = 0; - - out: - rtl8366_smi_stop(smi); - spin_unlock_irqrestore(&smi->lock, flags); - - return ret; -} -/* Read/write via mdiobus */ -#define MDC_MDIO_CTRL0_REG 31 -#define MDC_MDIO_START_REG 29 -#define MDC_MDIO_CTRL1_REG 21 -#define MDC_MDIO_ADDRESS_REG 23 -#define MDC_MDIO_DATA_WRITE_REG 24 -#define MDC_MDIO_DATA_READ_REG 25 - -#define MDC_MDIO_START_OP 0xFFFF -#define MDC_MDIO_ADDR_OP 0x000E -#define MDC_MDIO_READ_OP 0x0001 -#define MDC_MDIO_WRITE_OP 0x0003 -#define MDC_REALTEK_PHY_ADDR 0x0 - -int __rtl8366_mdio_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) -{ - u32 phy_id = MDC_REALTEK_PHY_ADDR; - struct mii_bus *mbus = smi->ext_mbus; - - BUG_ON(in_interrupt()); - - mutex_lock(&mbus->mdio_lock); - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write address control code to register 31 */ - mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write address to register 23 */ - mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write read control code to register 21 */ - mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_READ_OP); - - /* Write Start command to register 29 */ - mbus->write(smi->ext_mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Read data from register 25 */ - *data = mbus->read(mbus, phy_id, MDC_MDIO_DATA_READ_REG); - - mutex_unlock(&mbus->mdio_lock); - - return 0; -} - -static int __rtl8366_mdio_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) -{ - u32 phy_id = MDC_REALTEK_PHY_ADDR; - struct mii_bus *mbus = smi->ext_mbus; - - BUG_ON(in_interrupt()); - - mutex_lock(&mbus->mdio_lock); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write address control code to register 31 */ - mbus->write(mbus, phy_id, MDC_MDIO_CTRL0_REG, MDC_MDIO_ADDR_OP); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write address to register 23 */ - mbus->write(mbus, phy_id, MDC_MDIO_ADDRESS_REG, addr); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write data to register 24 */ - mbus->write(mbus, phy_id, MDC_MDIO_DATA_WRITE_REG, data); - - /* Write Start command to register 29 */ - mbus->write(mbus, phy_id, MDC_MDIO_START_REG, MDC_MDIO_START_OP); - - /* Write data control code to register 21 */ - mbus->write(mbus, phy_id, MDC_MDIO_CTRL1_REG, MDC_MDIO_WRITE_OP); - - mutex_unlock(&mbus->mdio_lock); - return 0; -} - -int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data) -{ - if (smi->ext_mbus) - return __rtl8366_mdio_read_reg(smi, addr, data); - else - return __rtl8366_smi_read_reg(smi, addr, data); -} -EXPORT_SYMBOL_GPL(rtl8366_smi_read_reg); - -static int __rtl8366_smi_write_reg(struct rtl8366_smi *smi, - u32 addr, u32 data, bool ack) -{ - unsigned long flags; - int ret; - - spin_lock_irqsave(&smi->lock, flags); - - rtl8366_smi_start(smi); - - /* send WRITE command */ - ret = rtl8366_smi_write_byte(smi, smi->cmd_write); - if (ret) - goto out; - - /* set ADDR[7:0] */ - ret = rtl8366_smi_write_byte(smi, addr & 0xff); - if (ret) - goto out; - - /* set ADDR[15:8] */ - ret = rtl8366_smi_write_byte(smi, addr >> 8); - if (ret) - goto out; - - /* write DATA[7:0] */ - ret = rtl8366_smi_write_byte(smi, data & 0xff); - if (ret) - goto out; - - /* write DATA[15:8] */ - if (ack) - ret = rtl8366_smi_write_byte(smi, data >> 8); - else - ret = rtl8366_smi_write_byte_noack(smi, data >> 8); - if (ret) - goto out; - - ret = 0; - - out: - rtl8366_smi_stop(smi); - spin_unlock_irqrestore(&smi->lock, flags); - - return ret; -} - -int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data) -{ - if (smi->ext_mbus) - return __rtl8366_mdio_write_reg(smi, addr, data); - else - return __rtl8366_smi_write_reg(smi, addr, data, true); -} -EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg); - -int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data) -{ - return __rtl8366_smi_write_reg(smi, addr, data, false); -} -EXPORT_SYMBOL_GPL(rtl8366_smi_write_reg_noack); - -int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data) -{ - u32 t; - int err; - - err = rtl8366_smi_read_reg(smi, addr, &t); - if (err) - return err; - - err = rtl8366_smi_write_reg(smi, addr, (t & ~mask) | data); - return err; - -} -EXPORT_SYMBOL_GPL(rtl8366_smi_rmwr); - -static int rtl8366_reset(struct rtl8366_smi *smi) -{ - if (smi->hw_reset) { - smi->hw_reset(smi, true); - msleep(RTL8366_SMI_HW_STOP_DELAY); - smi->hw_reset(smi, false); - msleep(RTL8366_SMI_HW_START_DELAY); - return 0; - } - - return smi->ops->reset_chip(smi); -} - -static int rtl8366_mc_is_used(struct rtl8366_smi *smi, int mc_index, int *used) -{ - int err; - int i; - - *used = 0; - for (i = 0; i < smi->num_ports; i++) { - int index = 0; - - err = smi->ops->get_mc_index(smi, i, &index); - if (err) - return err; - - if (mc_index == index) { - *used = 1; - break; - } - } - - return 0; -} - -static int rtl8366_set_vlan(struct rtl8366_smi *smi, int vid, u32 member, - u32 untag, u32 fid) -{ - struct rtl8366_vlan_4k vlan4k; - int err; - int i; - - /* Update the 4K table */ - err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); - if (err) - return err; - - vlan4k.member = member; - vlan4k.untag = untag; - vlan4k.fid = fid; - err = smi->ops->set_vlan_4k(smi, &vlan4k); - if (err) - return err; - - /* Try to find an existing MC entry for this VID */ - for (i = 0; i < smi->num_vlan_mc; i++) { - struct rtl8366_vlan_mc vlanmc; - - err = smi->ops->get_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - if (vid == vlanmc.vid) { - /* update the MC entry */ - vlanmc.member = member; - vlanmc.untag = untag; - vlanmc.fid = fid; - - err = smi->ops->set_vlan_mc(smi, i, &vlanmc); - break; - } - } - - return err; -} - -static int rtl8366_get_pvid(struct rtl8366_smi *smi, int port, int *val) -{ - struct rtl8366_vlan_mc vlanmc; - int err; - int index; - - err = smi->ops->get_mc_index(smi, port, &index); - if (err) - return err; - - err = smi->ops->get_vlan_mc(smi, index, &vlanmc); - if (err) - return err; - - *val = vlanmc.vid; - return 0; -} - -static int rtl8366_set_pvid(struct rtl8366_smi *smi, unsigned port, - unsigned vid) -{ - struct rtl8366_vlan_mc vlanmc; - struct rtl8366_vlan_4k vlan4k; - int err; - int i; - - /* Try to find an existing MC entry for this VID */ - for (i = 0; i < smi->num_vlan_mc; i++) { - err = smi->ops->get_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - if (vid == vlanmc.vid) { - err = smi->ops->set_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - err = smi->ops->set_mc_index(smi, port, i); - return err; - } - } - - /* We have no MC entry for this VID, try to find an empty one */ - for (i = 0; i < smi->num_vlan_mc; i++) { - err = smi->ops->get_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - if (vlanmc.vid == 0 && vlanmc.member == 0) { - /* Update the entry from the 4K table */ - err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); - if (err) - return err; - - vlanmc.vid = vid; - vlanmc.member = vlan4k.member; - vlanmc.untag = vlan4k.untag; - vlanmc.fid = vlan4k.fid; - err = smi->ops->set_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - err = smi->ops->set_mc_index(smi, port, i); - return err; - } - } - - /* MC table is full, try to find an unused entry and replace it */ - for (i = 0; i < smi->num_vlan_mc; i++) { - int used; - - err = rtl8366_mc_is_used(smi, i, &used); - if (err) - return err; - - if (!used) { - /* Update the entry from the 4K table */ - err = smi->ops->get_vlan_4k(smi, vid, &vlan4k); - if (err) - return err; - - vlanmc.vid = vid; - vlanmc.member = vlan4k.member; - vlanmc.untag = vlan4k.untag; - vlanmc.fid = vlan4k.fid; - err = smi->ops->set_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - - err = smi->ops->set_mc_index(smi, port, i); - return err; - } - } - - dev_err(smi->parent, - "all VLAN member configurations are in use\n"); - - return -ENOSPC; -} - -int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable) -{ - int err; - - err = smi->ops->enable_vlan(smi, enable); - if (err) - return err; - - smi->vlan_enabled = enable; - - if (!enable) { - smi->vlan4k_enabled = 0; - err = smi->ops->enable_vlan4k(smi, enable); - } - - return err; -} -EXPORT_SYMBOL_GPL(rtl8366_enable_vlan); - -static int rtl8366_enable_vlan4k(struct rtl8366_smi *smi, int enable) -{ - int err; - - if (enable) { - err = smi->ops->enable_vlan(smi, enable); - if (err) - return err; - - smi->vlan_enabled = enable; - } - - err = smi->ops->enable_vlan4k(smi, enable); - if (err) - return err; - - smi->vlan4k_enabled = enable; - return 0; -} - -int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable) -{ - int port; - int err; - - for (port = 0; port < smi->num_ports; port++) { - err = smi->ops->enable_port(smi, port, enable); - if (err) - return err; - } - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_enable_all_ports); - -int rtl8366_reset_vlan(struct rtl8366_smi *smi) -{ - struct rtl8366_vlan_mc vlanmc; - int err; - int i; - - rtl8366_enable_vlan(smi, 0); - rtl8366_enable_vlan4k(smi, 0); - - /* clear VLAN member configurations */ - vlanmc.vid = 0; - vlanmc.priority = 0; - vlanmc.member = 0; - vlanmc.untag = 0; - vlanmc.fid = 0; - for (i = 0; i < smi->num_vlan_mc; i++) { - err = smi->ops->set_vlan_mc(smi, i, &vlanmc); - if (err) - return err; - } - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_reset_vlan); - -static int rtl8366_init_vlan(struct rtl8366_smi *smi) -{ - int port; - int err; - - err = rtl8366_reset_vlan(smi); - if (err) - return err; - - for (port = 0; port < smi->num_ports; port++) { - u32 mask; - - if (port == smi->cpu_port) - mask = (1 << smi->num_ports) - 1; - else - mask = (1 << port) | (1 << smi->cpu_port); - - err = rtl8366_set_vlan(smi, (port + 1), mask, mask, 0); - if (err) - return err; - - err = rtl8366_set_pvid(smi, port, (port + 1)); - if (err) - return err; - } - - return rtl8366_enable_vlan(smi, 1); -} - -#ifdef CONFIG_RTL8366_SMI_DEBUG_FS -int rtl8366_debugfs_open(struct inode *inode, struct file *file) -{ - file->private_data = inode->i_private; - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_debugfs_open); - -static ssize_t rtl8366_read_debugfs_vlan_mc(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; - int i, len = 0; - char *buf = smi->buf; - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%2s %6s %4s %6s %6s %3s\n", - "id", "vid","prio", "member", "untag", "fid"); - - for (i = 0; i < smi->num_vlan_mc; ++i) { - struct rtl8366_vlan_mc vlanmc; - - smi->ops->get_vlan_mc(smi, i, &vlanmc); - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%2d %6d %4d 0x%04x 0x%04x %3d\n", - i, vlanmc.vid, vlanmc.priority, - vlanmc.member, vlanmc.untag, vlanmc.fid); - } - - return simple_read_from_buffer(user_buf, count, ppos, buf, len); -} - -#define RTL8366_VLAN4K_PAGE_SIZE 64 -#define RTL8366_VLAN4K_NUM_PAGES (4096 / RTL8366_VLAN4K_PAGE_SIZE) - -static ssize_t rtl8366_read_debugfs_vlan_4k(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; - int i, len = 0; - int offset; - char *buf = smi->buf; - - if (smi->dbg_vlan_4k_page >= RTL8366_VLAN4K_NUM_PAGES) { - len += snprintf(buf + len, sizeof(smi->buf) - len, - "invalid page: %u\n", smi->dbg_vlan_4k_page); - return simple_read_from_buffer(user_buf, count, ppos, buf, len); - } - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%4s %6s %6s %3s\n", - "vid", "member", "untag", "fid"); - - offset = RTL8366_VLAN4K_PAGE_SIZE * smi->dbg_vlan_4k_page; - for (i = 0; i < RTL8366_VLAN4K_PAGE_SIZE; i++) { - struct rtl8366_vlan_4k vlan4k; - - smi->ops->get_vlan_4k(smi, offset + i, &vlan4k); - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%4d 0x%04x 0x%04x %3d\n", - vlan4k.vid, vlan4k.member, - vlan4k.untag, vlan4k.fid); - } - - return simple_read_from_buffer(user_buf, count, ppos, buf, len); -} - -static ssize_t rtl8366_read_debugfs_pvid(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; - char *buf = smi->buf; - int len = 0; - int i; - - len += snprintf(buf + len, sizeof(smi->buf) - len, "%4s %4s\n", - "port", "pvid"); - - for (i = 0; i < smi->num_ports; i++) { - int pvid; - int err; - - err = rtl8366_get_pvid(smi, i, &pvid); - if (err) - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%4d error\n", i); - else - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%4d %4d\n", i, pvid); - } - - return simple_read_from_buffer(user_buf, count, ppos, buf, len); -} - -static ssize_t rtl8366_read_debugfs_reg(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; - u32 t, reg = smi->dbg_reg; - int err, len = 0; - char *buf = smi->buf; - - memset(buf, '\0', sizeof(smi->buf)); - - err = rtl8366_smi_read_reg(smi, reg, &t); - if (err) { - len += snprintf(buf, sizeof(smi->buf), - "Read failed (reg: 0x%04x)\n", reg); - return simple_read_from_buffer(user_buf, count, ppos, buf, len); - } - - len += snprintf(buf, sizeof(smi->buf), "reg = 0x%04x, val = 0x%04x\n", - reg, t); - - return simple_read_from_buffer(user_buf, count, ppos, buf, len); -} - -static ssize_t rtl8366_write_debugfs_reg(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = (struct rtl8366_smi *)file->private_data; - unsigned long data; - u32 reg = smi->dbg_reg; - int err; - size_t len; - char *buf = smi->buf; - - len = min(count, sizeof(smi->buf) - 1); - if (copy_from_user(buf, user_buf, len)) { - dev_err(smi->parent, "copy from user failed\n"); - return -EFAULT; - } - - buf[len] = '\0'; - if (len > 0 && buf[len - 1] == '\n') - buf[len - 1] = '\0'; - - - if (kstrtoul(buf, 16, &data)) { - dev_err(smi->parent, "Invalid reg value %s\n", buf); - } else { - err = rtl8366_smi_write_reg(smi, reg, data); - if (err) { - dev_err(smi->parent, - "writing reg 0x%04x val 0x%04lx failed\n", - reg, data); - } - } - - return count; -} - -static ssize_t rtl8366_read_debugfs_mibs(struct file *file, - char __user *user_buf, - size_t count, loff_t *ppos) -{ - struct rtl8366_smi *smi = file->private_data; - int i, j, len = 0; - char *buf = smi->buf; - - len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s", - "Counter"); - - for (i = 0; i < smi->num_ports; i++) { - char port_buf[10]; - - snprintf(port_buf, sizeof(port_buf), "Port %d", i); - len += snprintf(buf + len, sizeof(smi->buf) - len, " %12s", - port_buf); - } - len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); - - for (i = 0; i < smi->num_mib_counters; i++) { - len += snprintf(buf + len, sizeof(smi->buf) - len, "%-36s ", - smi->mib_counters[i].name); - for (j = 0; j < smi->num_ports; j++) { - unsigned long long counter = 0; - - if (!smi->ops->get_mib_counter(smi, i, j, &counter)) - len += snprintf(buf + len, - sizeof(smi->buf) - len, - "%12llu ", counter); - else - len += snprintf(buf + len, - sizeof(smi->buf) - len, - "%12s ", "error"); - } - len += snprintf(buf + len, sizeof(smi->buf) - len, "\n"); - } - - return simple_read_from_buffer(user_buf, count, ppos, buf, len); -} - -static const struct file_operations fops_rtl8366_regs = { - .read = rtl8366_read_debugfs_reg, - .write = rtl8366_write_debugfs_reg, - .open = rtl8366_debugfs_open, - .owner = THIS_MODULE -}; - -static const struct file_operations fops_rtl8366_vlan_mc = { - .read = rtl8366_read_debugfs_vlan_mc, - .open = rtl8366_debugfs_open, - .owner = THIS_MODULE -}; - -static const struct file_operations fops_rtl8366_vlan_4k = { - .read = rtl8366_read_debugfs_vlan_4k, - .open = rtl8366_debugfs_open, - .owner = THIS_MODULE -}; - -static const struct file_operations fops_rtl8366_pvid = { - .read = rtl8366_read_debugfs_pvid, - .open = rtl8366_debugfs_open, - .owner = THIS_MODULE -}; - -static const struct file_operations fops_rtl8366_mibs = { - .read = rtl8366_read_debugfs_mibs, - .open = rtl8366_debugfs_open, - .owner = THIS_MODULE -}; - -static void rtl8366_debugfs_init(struct rtl8366_smi *smi) -{ - struct dentry *node; - struct dentry *root; - - if (!smi->debugfs_root) - smi->debugfs_root = debugfs_create_dir(dev_name(smi->parent), - NULL); - - if (!smi->debugfs_root) { - dev_err(smi->parent, "Unable to create debugfs dir\n"); - return; - } - root = smi->debugfs_root; - - node = debugfs_create_x16("reg", S_IRUGO | S_IWUSR, root, - &smi->dbg_reg); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "reg"); - return; - } - - node = debugfs_create_file("val", S_IRUGO | S_IWUSR, root, smi, - &fops_rtl8366_regs); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "val"); - return; - } - - node = debugfs_create_file("vlan_mc", S_IRUSR, root, smi, - &fops_rtl8366_vlan_mc); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "vlan_mc"); - return; - } - - node = debugfs_create_u8("vlan_4k_page", S_IRUGO | S_IWUSR, root, - &smi->dbg_vlan_4k_page); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "vlan_4k_page"); - return; - } - - node = debugfs_create_file("vlan_4k", S_IRUSR, root, smi, - &fops_rtl8366_vlan_4k); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "vlan_4k"); - return; - } - - node = debugfs_create_file("pvid", S_IRUSR, root, smi, - &fops_rtl8366_pvid); - if (!node) { - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "pvid"); - return; - } - - node = debugfs_create_file("mibs", S_IRUSR, smi->debugfs_root, smi, - &fops_rtl8366_mibs); - if (!node) - dev_err(smi->parent, "Creating debugfs file '%s' failed\n", - "mibs"); -} - -static void rtl8366_debugfs_remove(struct rtl8366_smi *smi) -{ - if (smi->debugfs_root) { - debugfs_remove_recursive(smi->debugfs_root); - smi->debugfs_root = NULL; - } -} -#else -static inline void rtl8366_debugfs_init(struct rtl8366_smi *smi) {} -static inline void rtl8366_debugfs_remove(struct rtl8366_smi *smi) {} -#endif /* CONFIG_RTL8366_SMI_DEBUG_FS */ - -static int rtl8366_smi_mii_init(struct rtl8366_smi *smi) -{ - int ret; - -#ifdef CONFIG_OF - struct device_node *np = NULL; - - np = of_get_child_by_name(smi->parent->of_node, "mdio-bus"); -#endif - - smi->mii_bus = mdiobus_alloc(); - if (smi->mii_bus == NULL) { - ret = -ENOMEM; - goto err; - } - - smi->mii_bus->priv = (void *) smi; - smi->mii_bus->name = dev_name(smi->parent); - smi->mii_bus->read = smi->ops->mii_read; - smi->mii_bus->write = smi->ops->mii_write; - snprintf(smi->mii_bus->id, MII_BUS_ID_SIZE, "%s", - dev_name(smi->parent)); - smi->mii_bus->parent = smi->parent; - smi->mii_bus->phy_mask = ~(0x1f); -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0) - { - int i; - smi->mii_bus->irq = smi->mii_irq; - for (i = 0; i < PHY_MAX_ADDR; i++) - smi->mii_irq[i] = PHY_POLL; - } -#endif - -#ifdef CONFIG_OF - if (np) - ret = of_mdiobus_register(smi->mii_bus, np); - else -#endif - ret = mdiobus_register(smi->mii_bus); - - if (ret) - goto err_free; - - return 0; - - err_free: - mdiobus_free(smi->mii_bus); - err: - return ret; -} - -static void rtl8366_smi_mii_cleanup(struct rtl8366_smi *smi) -{ - mdiobus_unregister(smi->mii_bus); - mdiobus_free(smi->mii_bus); -} - -int rtl8366_sw_reset_switch(struct switch_dev *dev) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int err; - - err = rtl8366_reset(smi); - if (err) - return err; - - err = smi->ops->setup(smi); - if (err) - return err; - - err = rtl8366_reset_vlan(smi); - if (err) - return err; - - err = rtl8366_enable_vlan(smi, 1); - if (err) - return err; - - return rtl8366_enable_all_ports(smi, 1); -} -EXPORT_SYMBOL_GPL(rtl8366_sw_reset_switch); - -int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - return rtl8366_get_pvid(smi, port, val); -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_pvid); - -int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - return rtl8366_set_pvid(smi, port, val); -} -EXPORT_SYMBOL_GPL(rtl8366_sw_set_port_pvid); - -int rtl8366_sw_get_port_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int i, len = 0; - unsigned long long counter = 0; - char *buf = smi->buf; - - if (val->port_vlan >= smi->num_ports) - return -EINVAL; - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "Port %d MIB counters\n", - val->port_vlan); - - for (i = 0; i < smi->num_mib_counters; ++i) { - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%-36s: ", smi->mib_counters[i].name); - if (!smi->ops->get_mib_counter(smi, i, val->port_vlan, - &counter)) - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%llu\n", counter); - else - len += snprintf(buf + len, sizeof(smi->buf) - len, - "%s\n", "error"); - } - - val->value.s = buf; - val->len = len; - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_mib); - -int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats, - int txb_id, int rxb_id) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - unsigned long long counter = 0; - int ret; - - if (port >= smi->num_ports) - return -EINVAL; - - ret = smi->ops->get_mib_counter(smi, txb_id, port, &counter); - if (ret) - return ret; - - stats->tx_bytes = counter; - - ret = smi->ops->get_mib_counter(smi, rxb_id, port, &counter); - if (ret) - return ret; - - stats->rx_bytes = counter; - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_port_stats); - -int rtl8366_sw_get_vlan_info(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - int i; - u32 len = 0; - struct rtl8366_vlan_4k vlan4k; - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - char *buf = smi->buf; - int err; - - if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) - return -EINVAL; - - memset(buf, '\0', sizeof(smi->buf)); - - err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); - if (err) - return err; - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "VLAN %d: Ports: '", vlan4k.vid); - - for (i = 0; i < smi->num_ports; i++) { - if (!(vlan4k.member & (1 << i))) - continue; - - len += snprintf(buf + len, sizeof(smi->buf) - len, "%d%s", i, - (vlan4k.untag & (1 << i)) ? "" : "t"); - } - - len += snprintf(buf + len, sizeof(smi->buf) - len, - "', members=%04x, untag=%04x, fid=%u", - vlan4k.member, vlan4k.untag, vlan4k.fid); - - val->value.s = buf; - val->len = len; - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_info); - -int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - struct switch_port *port; - struct rtl8366_vlan_4k vlan4k; - int i; - - if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) - return -EINVAL; - - smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); - - port = &val->value.ports[0]; - val->len = 0; - for (i = 0; i < smi->num_ports; i++) { - if (!(vlan4k.member & BIT(i))) - continue; - - port->id = i; - port->flags = (vlan4k.untag & BIT(i)) ? - 0 : BIT(SWITCH_PORT_FLAG_TAGGED); - val->len++; - port++; - } - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_ports); - -int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - struct switch_port *port; - u32 member = 0; - u32 untag = 0; - int err; - int i; - - if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) - return -EINVAL; - - port = &val->value.ports[0]; - for (i = 0; i < val->len; i++, port++) { - int pvid = 0; - member |= BIT(port->id); - - if (!(port->flags & BIT(SWITCH_PORT_FLAG_TAGGED))) - untag |= BIT(port->id); - - /* - * To ensure that we have a valid MC entry for this VLAN, - * initialize the port VLAN ID here. - */ - err = rtl8366_get_pvid(smi, port->id, &pvid); - if (err < 0) - return err; - if (pvid == 0) { - err = rtl8366_set_pvid(smi, port->id, val->port_vlan); - if (err < 0) - return err; - } - } - - return rtl8366_set_vlan(smi, val->port_vlan, member, untag, 0); -} -EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_ports); - -int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_vlan_4k vlan4k; - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int err; - - if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) - return -EINVAL; - - err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); - if (err) - return err; - - val->value.i = vlan4k.fid; - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_fid); - -int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_vlan_4k vlan4k; - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int err; - - if (!smi->ops->is_vlan_valid(smi, val->port_vlan)) - return -EINVAL; - - if (val->value.i < 0 || val->value.i > attr->max) - return -EINVAL; - - err = smi->ops->get_vlan_4k(smi, val->port_vlan, &vlan4k); - if (err) - return err; - - return rtl8366_set_vlan(smi, val->port_vlan, - vlan4k.member, - vlan4k.untag, - val->value.i); -} -EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_fid); - -int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (attr->ofs > 2) - return -EINVAL; - - if (attr->ofs == 1) - val->value.i = smi->vlan_enabled; - else - val->value.i = smi->vlan4k_enabled; - - return 0; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_get_vlan_enable); - -int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int err; - - if (attr->ofs > 2) - return -EINVAL; - - if (attr->ofs == 1) - err = rtl8366_enable_vlan(smi, val->value.i); - else - err = rtl8366_enable_vlan4k(smi, val->value.i); - - return err; -} -EXPORT_SYMBOL_GPL(rtl8366_sw_set_vlan_enable); - -struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent) -{ - struct rtl8366_smi *smi; - - BUG_ON(!parent); - - smi = kzalloc(sizeof(*smi), GFP_KERNEL); - if (!smi) { - dev_err(parent, "no memory for private data\n"); - return NULL; - } - - smi->parent = parent; - return smi; -} -EXPORT_SYMBOL_GPL(rtl8366_smi_alloc); - -static int __rtl8366_smi_init(struct rtl8366_smi *smi, const char *name) -{ - int err; - - if (!smi->ext_mbus) { - err = gpio_request(smi->gpio_sda, name); - if (err) { - printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", - smi->gpio_sda, err); - goto err_out; - } - - err = gpio_request(smi->gpio_sck, name); - if (err) { - printk(KERN_ERR "rtl8366_smi: gpio_request failed for %u, err=%d\n", - smi->gpio_sck, err); - goto err_free_sda; - } - } - - spin_lock_init(&smi->lock); - - /* start the switch */ - if (smi->hw_reset) { - smi->hw_reset(smi, false); - msleep(RTL8366_SMI_HW_START_DELAY); - } - - return 0; - - err_free_sda: - gpio_free(smi->gpio_sda); - err_out: - return err; -} - -static void __rtl8366_smi_cleanup(struct rtl8366_smi *smi) -{ - if (smi->hw_reset) - smi->hw_reset(smi, true); - - if (!smi->ext_mbus) { - gpio_free(smi->gpio_sck); - gpio_free(smi->gpio_sda); - } -} - -enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata) -{ - static struct rtl8366_smi smi; - enum rtl8366_type type = RTL8366_TYPE_UNKNOWN; - u32 reg = 0; - - memset(&smi, 0, sizeof(smi)); - smi.gpio_sda = pdata->gpio_sda; - smi.gpio_sck = pdata->gpio_sck; - smi.clk_delay = 10; - smi.cmd_read = 0xa9; - smi.cmd_write = 0xa8; - - if (__rtl8366_smi_init(&smi, "rtl8366")) - goto out; - - if (rtl8366_smi_read_reg(&smi, 0x5c, ®)) - goto cleanup; - - switch(reg) { - case 0x6027: - printk("Found an RTL8366S switch\n"); - type = RTL8366_TYPE_S; - break; - case 0x5937: - printk("Found an RTL8366RB switch\n"); - type = RTL8366_TYPE_RB; - break; - default: - printk("Found an Unknown RTL8366 switch (id=0x%04x)\n", reg); - break; - } - -cleanup: - __rtl8366_smi_cleanup(&smi); -out: - return type; -} - -int rtl8366_smi_init(struct rtl8366_smi *smi) -{ - int err; - - if (!smi->ops) - return -EINVAL; - - err = __rtl8366_smi_init(smi, dev_name(smi->parent)); - if (err) - goto err_out; - - if (!smi->ext_mbus) - dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n", - smi->gpio_sda, smi->gpio_sck); - else - dev_info(smi->parent, "using MDIO bus '%s'\n", smi->ext_mbus->name); - - err = smi->ops->detect(smi); - if (err) { - dev_err(smi->parent, "chip detection failed, err=%d\n", err); - goto err_free_sck; - } - - err = rtl8366_reset(smi); - if (err) - goto err_free_sck; - - err = smi->ops->setup(smi); - if (err) { - dev_err(smi->parent, "chip setup failed, err=%d\n", err); - goto err_free_sck; - } - - err = rtl8366_init_vlan(smi); - if (err) { - dev_err(smi->parent, "VLAN initialization failed, err=%d\n", - err); - goto err_free_sck; - } - - err = rtl8366_enable_all_ports(smi, 1); - if (err) - goto err_free_sck; - - err = rtl8366_smi_mii_init(smi); - if (err) - goto err_free_sck; - - rtl8366_debugfs_init(smi); - - return 0; - - err_free_sck: - __rtl8366_smi_cleanup(smi); - err_out: - return err; -} -EXPORT_SYMBOL_GPL(rtl8366_smi_init); - -void rtl8366_smi_cleanup(struct rtl8366_smi *smi) -{ - rtl8366_debugfs_remove(smi); - rtl8366_smi_mii_cleanup(smi); - __rtl8366_smi_cleanup(smi); -} -EXPORT_SYMBOL_GPL(rtl8366_smi_cleanup); - -#ifdef CONFIG_OF -static void rtl8366_smi_reset(struct rtl8366_smi *smi, bool active) -{ - if (active) - reset_control_assert(smi->reset); - else - reset_control_deassert(smi->reset); -} - -int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) -{ - int sck = of_get_named_gpio(pdev->dev.of_node, "gpio-sck", 0); - int sda = of_get_named_gpio(pdev->dev.of_node, "gpio-sda", 0); - struct device_node *np = pdev->dev.of_node; - struct device_node *mdio_node; - - mdio_node = of_parse_phandle(np, "mii-bus", 0); - if (!mdio_node) { - dev_err(&pdev->dev, "cannot find mdio node phandle"); - goto try_gpio; - } - - smi->ext_mbus = of_mdio_find_bus(mdio_node); - if (!smi->ext_mbus) { - dev_err(&pdev->dev, - "cannot find mdio bus from bus handle"); - goto try_gpio; - } - - return 0; - -try_gpio: - if (!gpio_is_valid(sck) || !gpio_is_valid(sda)) { - dev_err(&pdev->dev, "gpios missing in devictree\n"); - return -EINVAL; - } - - smi->gpio_sda = sda; - smi->gpio_sck = sck; - smi->reset = devm_reset_control_get(&pdev->dev, "switch"); - if (!IS_ERR(smi->reset)) - smi->hw_reset = rtl8366_smi_reset; - - return 0; -} -#else -static inline int rtl8366_smi_probe_of(struct platform_device *pdev, struct rtl8366_smi *smi) -{ - return -ENODEV; -} -#endif - -int rtl8366_smi_probe_plat(struct platform_device *pdev, struct rtl8366_smi *smi) -{ - struct rtl8366_platform_data *pdata = pdev->dev.platform_data; - - if (!pdev->dev.platform_data) { - dev_err(&pdev->dev, "no platform data specified\n"); - return -EINVAL; - } - - smi->gpio_sda = pdata->gpio_sda; - smi->gpio_sck = pdata->gpio_sck; - smi->hw_reset = pdata->hw_reset; - - return 0; -} - - -struct rtl8366_smi *rtl8366_smi_probe(struct platform_device *pdev) -{ - struct rtl8366_smi *smi; - int err; - - smi = rtl8366_smi_alloc(&pdev->dev); - if (!smi) - return NULL; - - if (pdev->dev.of_node) - err = rtl8366_smi_probe_of(pdev, smi); - else - err = rtl8366_smi_probe_plat(pdev, smi); - - if (err) - goto free_smi; - - return smi; - -free_smi: - kfree(smi); - return NULL; -} -EXPORT_SYMBOL_GPL(rtl8366_smi_probe); - -MODULE_DESCRIPTION("Realtek RTL8366 SMI interface driver"); -MODULE_AUTHOR("Gabor Juhos "); -MODULE_LICENSE("GPL v2"); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h b/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h deleted file mode 100644 index d1d988a372..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Realtek RTL8366 SMI interface driver defines - * - * Copyright (C) 2009-2010 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#ifndef _RTL8366_SMI_H -#define _RTL8366_SMI_H - -#include -#include -#include -#include - -struct rtl8366_smi_ops; -struct rtl8366_vlan_ops; -struct mii_bus; -struct dentry; -struct inode; -struct file; - -struct rtl8366_mib_counter { - unsigned base; - unsigned offset; - unsigned length; - const char *name; -}; - -struct rtl8366_smi { - struct device *parent; - unsigned int gpio_sda; - unsigned int gpio_sck; - void (*hw_reset)(struct rtl8366_smi *smi, bool active); - unsigned int clk_delay; /* ns */ - u8 cmd_read; - u8 cmd_write; - spinlock_t lock; - struct mii_bus *mii_bus; - int mii_irq[PHY_MAX_ADDR]; - struct switch_dev sw_dev; - - unsigned int cpu_port; - unsigned int num_ports; - unsigned int num_vlan_mc; - unsigned int num_mib_counters; - struct rtl8366_mib_counter *mib_counters; - - struct rtl8366_smi_ops *ops; - - int vlan_enabled; - int vlan4k_enabled; - - char buf[4096]; - - struct reset_control *reset; - -#ifdef CONFIG_RTL8366_SMI_DEBUG_FS - struct dentry *debugfs_root; - u16 dbg_reg; - u8 dbg_vlan_4k_page; -#endif - struct mii_bus *ext_mbus; -}; - -struct rtl8366_vlan_mc { - u16 vid; - u16 untag; - u16 member; - u8 fid; - u8 priority; -}; - -struct rtl8366_vlan_4k { - u16 vid; - u16 untag; - u16 member; - u8 fid; -}; - -struct rtl8366_smi_ops { - int (*detect)(struct rtl8366_smi *smi); - int (*reset_chip)(struct rtl8366_smi *smi); - int (*setup)(struct rtl8366_smi *smi); - - int (*mii_read)(struct mii_bus *bus, int addr, int reg); - int (*mii_write)(struct mii_bus *bus, int addr, int reg, u16 val); - - int (*get_vlan_mc)(struct rtl8366_smi *smi, u32 index, - struct rtl8366_vlan_mc *vlanmc); - int (*set_vlan_mc)(struct rtl8366_smi *smi, u32 index, - const struct rtl8366_vlan_mc *vlanmc); - int (*get_vlan_4k)(struct rtl8366_smi *smi, u32 vid, - struct rtl8366_vlan_4k *vlan4k); - int (*set_vlan_4k)(struct rtl8366_smi *smi, - const struct rtl8366_vlan_4k *vlan4k); - int (*get_mc_index)(struct rtl8366_smi *smi, int port, int *val); - int (*set_mc_index)(struct rtl8366_smi *smi, int port, int index); - int (*get_mib_counter)(struct rtl8366_smi *smi, int counter, - int port, unsigned long long *val); - int (*is_vlan_valid)(struct rtl8366_smi *smi, unsigned vlan); - int (*enable_vlan)(struct rtl8366_smi *smi, int enable); - int (*enable_vlan4k)(struct rtl8366_smi *smi, int enable); - int (*enable_port)(struct rtl8366_smi *smi, int port, int enable); -}; - -struct rtl8366_smi *rtl8366_smi_alloc(struct device *parent); -int rtl8366_smi_init(struct rtl8366_smi *smi); -void rtl8366_smi_cleanup(struct rtl8366_smi *smi); -int rtl8366_smi_write_reg(struct rtl8366_smi *smi, u32 addr, u32 data); -int rtl8366_smi_write_reg_noack(struct rtl8366_smi *smi, u32 addr, u32 data); -int rtl8366_smi_read_reg(struct rtl8366_smi *smi, u32 addr, u32 *data); -int rtl8366_smi_rmwr(struct rtl8366_smi *smi, u32 addr, u32 mask, u32 data); - -int rtl8366_reset_vlan(struct rtl8366_smi *smi); -int rtl8366_enable_vlan(struct rtl8366_smi *smi, int enable); -int rtl8366_enable_all_ports(struct rtl8366_smi *smi, int enable); - -#ifdef CONFIG_RTL8366_SMI_DEBUG_FS -int rtl8366_debugfs_open(struct inode *inode, struct file *file); -#endif - -static inline struct rtl8366_smi *sw_to_rtl8366_smi(struct switch_dev *sw) -{ - return container_of(sw, struct rtl8366_smi, sw_dev); -} - -int rtl8366_sw_reset_switch(struct switch_dev *dev); -int rtl8366_sw_get_port_pvid(struct switch_dev *dev, int port, int *val); -int rtl8366_sw_set_port_pvid(struct switch_dev *dev, int port, int val); -int rtl8366_sw_get_port_mib(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_get_vlan_info(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_get_vlan_fid(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_set_vlan_fid(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_get_vlan_ports(struct switch_dev *dev, struct switch_val *val); -int rtl8366_sw_set_vlan_ports(struct switch_dev *dev, struct switch_val *val); -int rtl8366_sw_get_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_set_vlan_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val); -int rtl8366_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats, - int txb_id, int rxb_id); - -struct rtl8366_smi* rtl8366_smi_probe(struct platform_device *pdev); - -#endif /* _RTL8366_SMI_H */ diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366rb.c b/target/linux/generic/files/drivers/net/phy/rtl8366rb.c deleted file mode 100644 index dc394c02b6..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8366rb.c +++ /dev/null @@ -1,1532 +0,0 @@ -/* - * Platform driver for the Realtek RTL8366RB ethernet switch - * - * Copyright (C) 2009-2010 Gabor Juhos - * Copyright (C) 2010 Antti Seppälä - * Copyright (C) 2010 Roman Yeryomin - * Copyright (C) 2011 Colin Leitner - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rtl8366_smi.h" - -#define RTL8366RB_DRIVER_DESC "Realtek RTL8366RB ethernet switch driver" -#define RTL8366RB_DRIVER_VER "0.2.4" - -#define RTL8366RB_PHY_NO_MAX 4 -#define RTL8366RB_PHY_PAGE_MAX 7 -#define RTL8366RB_PHY_ADDR_MAX 31 - -/* Switch Global Configuration register */ -#define RTL8366RB_SGCR 0x0000 -#define RTL8366RB_SGCR_EN_BC_STORM_CTRL BIT(0) -#define RTL8366RB_SGCR_MAX_LENGTH(_x) (_x << 4) -#define RTL8366RB_SGCR_MAX_LENGTH_MASK RTL8366RB_SGCR_MAX_LENGTH(0x3) -#define RTL8366RB_SGCR_MAX_LENGTH_1522 RTL8366RB_SGCR_MAX_LENGTH(0x0) -#define RTL8366RB_SGCR_MAX_LENGTH_1536 RTL8366RB_SGCR_MAX_LENGTH(0x1) -#define RTL8366RB_SGCR_MAX_LENGTH_1552 RTL8366RB_SGCR_MAX_LENGTH(0x2) -#define RTL8366RB_SGCR_MAX_LENGTH_9216 RTL8366RB_SGCR_MAX_LENGTH(0x3) -#define RTL8366RB_SGCR_EN_VLAN BIT(13) -#define RTL8366RB_SGCR_EN_VLAN_4KTB BIT(14) - -/* Port Enable Control register */ -#define RTL8366RB_PECR 0x0001 - -/* Port Mirror Control Register */ -#define RTL8366RB_PMCR 0x0007 -#define RTL8366RB_PMCR_SOURCE_PORT(_x) (_x) -#define RTL8366RB_PMCR_SOURCE_PORT_MASK 0x000f -#define RTL8366RB_PMCR_MONITOR_PORT(_x) ((_x) << 4) -#define RTL8366RB_PMCR_MONITOR_PORT_MASK 0x00f0 -#define RTL8366RB_PMCR_MIRROR_RX BIT(8) -#define RTL8366RB_PMCR_MIRROR_TX BIT(9) -#define RTL8366RB_PMCR_MIRROR_SPC BIT(10) -#define RTL8366RB_PMCR_MIRROR_ISO BIT(11) - -/* Switch Security Control registers */ -#define RTL8366RB_SSCR0 0x0002 -#define RTL8366RB_SSCR1 0x0003 -#define RTL8366RB_SSCR2 0x0004 -#define RTL8366RB_SSCR2_DROP_UNKNOWN_DA BIT(0) - -#define RTL8366RB_RESET_CTRL_REG 0x0100 -#define RTL8366RB_CHIP_CTRL_RESET_HW 1 -#define RTL8366RB_CHIP_CTRL_RESET_SW (1 << 1) - -#define RTL8366RB_CHIP_VERSION_CTRL_REG 0x050A -#define RTL8366RB_CHIP_VERSION_MASK 0xf -#define RTL8366RB_CHIP_ID_REG 0x0509 -#define RTL8366RB_CHIP_ID_8366 0x5937 - -/* PHY registers control */ -#define RTL8366RB_PHY_ACCESS_CTRL_REG 0x8000 -#define RTL8366RB_PHY_ACCESS_DATA_REG 0x8002 - -#define RTL8366RB_PHY_CTRL_READ 1 -#define RTL8366RB_PHY_CTRL_WRITE 0 - -#define RTL8366RB_PHY_REG_MASK 0x1f -#define RTL8366RB_PHY_PAGE_OFFSET 5 -#define RTL8366RB_PHY_PAGE_MASK (0xf << 5) -#define RTL8366RB_PHY_NO_OFFSET 9 -#define RTL8366RB_PHY_NO_MASK (0x1f << 9) - -#define RTL8366RB_VLAN_INGRESS_CTRL2_REG 0x037f - -/* LED control registers */ -#define RTL8366RB_LED_BLINKRATE_REG 0x0430 -#define RTL8366RB_LED_BLINKRATE_BIT 0 -#define RTL8366RB_LED_BLINKRATE_MASK 0x0007 - -#define RTL8366RB_LED_CTRL_REG 0x0431 -#define RTL8366RB_LED_0_1_CTRL_REG 0x0432 -#define RTL8366RB_LED_2_3_CTRL_REG 0x0433 - -#define RTL8366RB_MIB_COUNT 33 -#define RTL8366RB_GLOBAL_MIB_COUNT 1 -#define RTL8366RB_MIB_COUNTER_PORT_OFFSET 0x0050 -#define RTL8366RB_MIB_COUNTER_BASE 0x1000 -#define RTL8366RB_MIB_CTRL_REG 0x13F0 -#define RTL8366RB_MIB_CTRL_USER_MASK 0x0FFC -#define RTL8366RB_MIB_CTRL_BUSY_MASK BIT(0) -#define RTL8366RB_MIB_CTRL_RESET_MASK BIT(1) -#define RTL8366RB_MIB_CTRL_PORT_RESET(_p) BIT(2 + (_p)) -#define RTL8366RB_MIB_CTRL_GLOBAL_RESET BIT(11) - -#define RTL8366RB_PORT_VLAN_CTRL_BASE 0x0063 -#define RTL8366RB_PORT_VLAN_CTRL_REG(_p) \ - (RTL8366RB_PORT_VLAN_CTRL_BASE + (_p) / 4) -#define RTL8366RB_PORT_VLAN_CTRL_MASK 0xf -#define RTL8366RB_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) - - -#define RTL8366RB_VLAN_TABLE_READ_BASE 0x018C -#define RTL8366RB_VLAN_TABLE_WRITE_BASE 0x0185 - - -#define RTL8366RB_TABLE_ACCESS_CTRL_REG 0x0180 -#define RTL8366RB_TABLE_VLAN_READ_CTRL 0x0E01 -#define RTL8366RB_TABLE_VLAN_WRITE_CTRL 0x0F01 - -#define RTL8366RB_VLAN_MC_BASE(_x) (0x0020 + (_x) * 3) - - -#define RTL8366RB_PORT_LINK_STATUS_BASE 0x0014 -#define RTL8366RB_PORT_STATUS_SPEED_MASK 0x0003 -#define RTL8366RB_PORT_STATUS_DUPLEX_MASK 0x0004 -#define RTL8366RB_PORT_STATUS_LINK_MASK 0x0010 -#define RTL8366RB_PORT_STATUS_TXPAUSE_MASK 0x0020 -#define RTL8366RB_PORT_STATUS_RXPAUSE_MASK 0x0040 -#define RTL8366RB_PORT_STATUS_AN_MASK 0x0080 - - -#define RTL8366RB_PORT_NUM_CPU 5 -#define RTL8366RB_NUM_PORTS 6 -#define RTL8366RB_NUM_VLANS 16 -#define RTL8366RB_NUM_LEDGROUPS 4 -#define RTL8366RB_NUM_VIDS 4096 -#define RTL8366RB_PRIORITYMAX 7 -#define RTL8366RB_FIDMAX 7 - - -#define RTL8366RB_PORT_1 (1 << 0) /* In userspace port 0 */ -#define RTL8366RB_PORT_2 (1 << 1) /* In userspace port 1 */ -#define RTL8366RB_PORT_3 (1 << 2) /* In userspace port 2 */ -#define RTL8366RB_PORT_4 (1 << 3) /* In userspace port 3 */ -#define RTL8366RB_PORT_5 (1 << 4) /* In userspace port 4 */ - -#define RTL8366RB_PORT_CPU (1 << 5) /* CPU port */ - -#define RTL8366RB_PORT_ALL (RTL8366RB_PORT_1 | \ - RTL8366RB_PORT_2 | \ - RTL8366RB_PORT_3 | \ - RTL8366RB_PORT_4 | \ - RTL8366RB_PORT_5 | \ - RTL8366RB_PORT_CPU) - -#define RTL8366RB_PORT_ALL_BUT_CPU (RTL8366RB_PORT_1 | \ - RTL8366RB_PORT_2 | \ - RTL8366RB_PORT_3 | \ - RTL8366RB_PORT_4 | \ - RTL8366RB_PORT_5) - -#define RTL8366RB_PORT_ALL_EXTERNAL (RTL8366RB_PORT_1 | \ - RTL8366RB_PORT_2 | \ - RTL8366RB_PORT_3 | \ - RTL8366RB_PORT_4) - -#define RTL8366RB_PORT_ALL_INTERNAL RTL8366RB_PORT_CPU - -#define RTL8366RB_VLAN_VID_MASK 0xfff -#define RTL8366RB_VLAN_PRIORITY_SHIFT 12 -#define RTL8366RB_VLAN_PRIORITY_MASK 0x7 -#define RTL8366RB_VLAN_UNTAG_SHIFT 8 -#define RTL8366RB_VLAN_UNTAG_MASK 0xff -#define RTL8366RB_VLAN_MEMBER_MASK 0xff -#define RTL8366RB_VLAN_FID_MASK 0x7 - - -/* Port ingress bandwidth control */ -#define RTL8366RB_IB_BASE 0x0200 -#define RTL8366RB_IB_REG(pnum) (RTL8366RB_IB_BASE + pnum) -#define RTL8366RB_IB_BDTH_MASK 0x3fff -#define RTL8366RB_IB_PREIFG_OFFSET 14 -#define RTL8366RB_IB_PREIFG_MASK (1 << RTL8366RB_IB_PREIFG_OFFSET) - -/* Port egress bandwidth control */ -#define RTL8366RB_EB_BASE 0x02d1 -#define RTL8366RB_EB_REG(pnum) (RTL8366RB_EB_BASE + pnum) -#define RTL8366RB_EB_BDTH_MASK 0x3fff -#define RTL8366RB_EB_PREIFG_REG 0x02f8 -#define RTL8366RB_EB_PREIFG_OFFSET 9 -#define RTL8366RB_EB_PREIFG_MASK (1 << RTL8366RB_EB_PREIFG_OFFSET) - -#define RTL8366RB_BDTH_SW_MAX 1048512 -#define RTL8366RB_BDTH_UNIT 64 -#define RTL8366RB_BDTH_REG_DEFAULT 16383 - -/* QOS */ -#define RTL8366RB_QOS_BIT 15 -#define RTL8366RB_QOS_MASK (1 << RTL8366RB_QOS_BIT) -/* Include/Exclude Preamble and IFG (20 bytes). 0:Exclude, 1:Include. */ -#define RTL8366RB_QOS_DEFAULT_PREIFG 1 - - -#define RTL8366RB_MIB_RXB_ID 0 /* IfInOctets */ -#define RTL8366RB_MIB_TXB_ID 20 /* IfOutOctets */ - -static struct rtl8366_mib_counter rtl8366rb_mib_counters[] = { - { 0, 0, 4, "IfInOctets" }, - { 0, 4, 4, "EtherStatsOctets" }, - { 0, 8, 2, "EtherStatsUnderSizePkts" }, - { 0, 10, 2, "EtherFragments" }, - { 0, 12, 2, "EtherStatsPkts64Octets" }, - { 0, 14, 2, "EtherStatsPkts65to127Octets" }, - { 0, 16, 2, "EtherStatsPkts128to255Octets" }, - { 0, 18, 2, "EtherStatsPkts256to511Octets" }, - { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, - { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, - { 0, 24, 2, "EtherOversizeStats" }, - { 0, 26, 2, "EtherStatsJabbers" }, - { 0, 28, 2, "IfInUcastPkts" }, - { 0, 30, 2, "EtherStatsMulticastPkts" }, - { 0, 32, 2, "EtherStatsBroadcastPkts" }, - { 0, 34, 2, "EtherStatsDropEvents" }, - { 0, 36, 2, "Dot3StatsFCSErrors" }, - { 0, 38, 2, "Dot3StatsSymbolErrors" }, - { 0, 40, 2, "Dot3InPauseFrames" }, - { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, - { 0, 44, 4, "IfOutOctets" }, - { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, - { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, - { 0, 52, 2, "Dot3sDeferredTransmissions" }, - { 0, 54, 2, "Dot3StatsLateCollisions" }, - { 0, 56, 2, "EtherStatsCollisions" }, - { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, - { 0, 60, 2, "Dot3OutPauseFrames" }, - { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, - { 0, 64, 2, "Dot1dTpPortInDiscards" }, - { 0, 66, 2, "IfOutUcastPkts" }, - { 0, 68, 2, "IfOutMulticastPkts" }, - { 0, 70, 2, "IfOutBroadcastPkts" }, -}; - -#define REG_WR(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_write_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_RMW(_smi, _reg, _mask, _val) \ - do { \ - err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ - if (err) \ - return err; \ - } while (0) - -static int rtl8366rb_reset_chip(struct rtl8366_smi *smi) -{ - int timeout = 10; - u32 data; - - rtl8366_smi_write_reg_noack(smi, RTL8366RB_RESET_CTRL_REG, - RTL8366RB_CHIP_CTRL_RESET_HW); - do { - msleep(1); - if (rtl8366_smi_read_reg(smi, RTL8366RB_RESET_CTRL_REG, &data)) - return -EIO; - - if (!(data & RTL8366RB_CHIP_CTRL_RESET_HW)) - break; - } while (--timeout); - - if (!timeout) { - printk("Timeout waiting for the switch to reset\n"); - return -EIO; - } - - return 0; -} - -static int rtl8366rb_setup(struct rtl8366_smi *smi) -{ - int err; -#ifdef CONFIG_OF - unsigned i; - struct device_node *np; - unsigned num_initvals; - const __be32 *paddr; - - np = smi->parent->of_node; - - paddr = of_get_property(np, "realtek,initvals", &num_initvals); - if (paddr) { - dev_info(smi->parent, "applying initvals from DTS\n"); - - if (num_initvals < (2 * sizeof(*paddr))) - return -EINVAL; - - num_initvals /= sizeof(*paddr); - - for (i = 0; i < num_initvals - 1; i += 2) { - u32 reg = be32_to_cpup(paddr + i); - u32 val = be32_to_cpup(paddr + i + 1); - - REG_WR(smi, reg, val); - } - } -#endif - - /* set maximum packet length to 1536 bytes */ - REG_RMW(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_MAX_LENGTH_MASK, - RTL8366RB_SGCR_MAX_LENGTH_1536); - - /* enable learning for all ports */ - REG_WR(smi, RTL8366RB_SSCR0, 0); - - /* enable auto ageing for all ports */ - REG_WR(smi, RTL8366RB_SSCR1, 0); - - /* - * discard VLAN tagged packets if the port is not a member of - * the VLAN with which the packets is associated. - */ - REG_WR(smi, RTL8366RB_VLAN_INGRESS_CTRL2_REG, RTL8366RB_PORT_ALL); - - /* don't drop packets whose DA has not been learned */ - REG_RMW(smi, RTL8366RB_SSCR2, RTL8366RB_SSCR2_DROP_UNKNOWN_DA, 0); - - return 0; -} - -static int rtl8366rb_read_phy_reg(struct rtl8366_smi *smi, - u32 phy_no, u32 page, u32 addr, u32 *data) -{ - u32 reg; - int ret; - - if (phy_no > RTL8366RB_PHY_NO_MAX) - return -EINVAL; - - if (page > RTL8366RB_PHY_PAGE_MAX) - return -EINVAL; - - if (addr > RTL8366RB_PHY_ADDR_MAX) - return -EINVAL; - - ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, - RTL8366RB_PHY_CTRL_READ); - if (ret) - return ret; - - reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | - ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | - (addr & RTL8366RB_PHY_REG_MASK); - - ret = rtl8366_smi_write_reg(smi, reg, 0); - if (ret) - return ret; - - ret = rtl8366_smi_read_reg(smi, RTL8366RB_PHY_ACCESS_DATA_REG, data); - if (ret) - return ret; - - return 0; -} - -static int rtl8366rb_write_phy_reg(struct rtl8366_smi *smi, - u32 phy_no, u32 page, u32 addr, u32 data) -{ - u32 reg; - int ret; - - if (phy_no > RTL8366RB_PHY_NO_MAX) - return -EINVAL; - - if (page > RTL8366RB_PHY_PAGE_MAX) - return -EINVAL; - - if (addr > RTL8366RB_PHY_ADDR_MAX) - return -EINVAL; - - ret = rtl8366_smi_write_reg(smi, RTL8366RB_PHY_ACCESS_CTRL_REG, - RTL8366RB_PHY_CTRL_WRITE); - if (ret) - return ret; - - reg = 0x8000 | (1 << (phy_no + RTL8366RB_PHY_NO_OFFSET)) | - ((page << RTL8366RB_PHY_PAGE_OFFSET) & RTL8366RB_PHY_PAGE_MASK) | - (addr & RTL8366RB_PHY_REG_MASK); - - ret = rtl8366_smi_write_reg(smi, reg, data); - if (ret) - return ret; - - return 0; -} - -static int rtl8366rb_get_mib_counter(struct rtl8366_smi *smi, int counter, - int port, unsigned long long *val) -{ - int i; - int err; - u32 addr, data; - u64 mibvalue; - - if (port > RTL8366RB_NUM_PORTS || counter >= RTL8366RB_MIB_COUNT) - return -EINVAL; - - addr = RTL8366RB_MIB_COUNTER_BASE + - RTL8366RB_MIB_COUNTER_PORT_OFFSET * (port) + - rtl8366rb_mib_counters[counter].offset; - - /* - * Writing access counter address first - * then ASIC will prepare 64bits counter wait for being retrived - */ - data = 0; /* writing data will be discard by ASIC */ - err = rtl8366_smi_write_reg(smi, addr, data); - if (err) - return err; - - /* read MIB control register */ - err = rtl8366_smi_read_reg(smi, RTL8366RB_MIB_CTRL_REG, &data); - if (err) - return err; - - if (data & RTL8366RB_MIB_CTRL_BUSY_MASK) - return -EBUSY; - - if (data & RTL8366RB_MIB_CTRL_RESET_MASK) - return -EIO; - - mibvalue = 0; - for (i = rtl8366rb_mib_counters[counter].length; i > 0; i--) { - err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); - if (err) - return err; - - mibvalue = (mibvalue << 16) | (data & 0xFFFF); - } - - *val = mibvalue; - return 0; -} - -static int rtl8366rb_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, - struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[3]; - int err; - int i; - - memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); - - if (vid >= RTL8366RB_NUM_VIDS) - return -EINVAL; - - /* write VID */ - err = rtl8366_smi_write_reg(smi, RTL8366RB_VLAN_TABLE_WRITE_BASE, - vid & RTL8366RB_VLAN_VID_MASK); - if (err) - return err; - - /* write table access control word */ - err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, - RTL8366RB_TABLE_VLAN_READ_CTRL); - if (err) - return err; - - for (i = 0; i < 3; i++) { - err = rtl8366_smi_read_reg(smi, - RTL8366RB_VLAN_TABLE_READ_BASE + i, - &data[i]); - if (err) - return err; - } - - vlan4k->vid = vid; - vlan4k->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & - RTL8366RB_VLAN_UNTAG_MASK; - vlan4k->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; - vlan4k->fid = data[2] & RTL8366RB_VLAN_FID_MASK; - - return 0; -} - -static int rtl8366rb_set_vlan_4k(struct rtl8366_smi *smi, - const struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[3]; - int err; - int i; - - if (vlan4k->vid >= RTL8366RB_NUM_VIDS || - vlan4k->member > RTL8366RB_VLAN_MEMBER_MASK || - vlan4k->untag > RTL8366RB_VLAN_UNTAG_MASK || - vlan4k->fid > RTL8366RB_FIDMAX) - return -EINVAL; - - data[0] = vlan4k->vid & RTL8366RB_VLAN_VID_MASK; - data[1] = (vlan4k->member & RTL8366RB_VLAN_MEMBER_MASK) | - ((vlan4k->untag & RTL8366RB_VLAN_UNTAG_MASK) << - RTL8366RB_VLAN_UNTAG_SHIFT); - data[2] = vlan4k->fid & RTL8366RB_VLAN_FID_MASK; - - for (i = 0; i < 3; i++) { - err = rtl8366_smi_write_reg(smi, - RTL8366RB_VLAN_TABLE_WRITE_BASE + i, - data[i]); - if (err) - return err; - } - - /* write table access control word */ - err = rtl8366_smi_write_reg(smi, RTL8366RB_TABLE_ACCESS_CTRL_REG, - RTL8366RB_TABLE_VLAN_WRITE_CTRL); - - return err; -} - -static int rtl8366rb_get_vlan_mc(struct rtl8366_smi *smi, u32 index, - struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[3]; - int err; - int i; - - memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); - - if (index >= RTL8366RB_NUM_VLANS) - return -EINVAL; - - for (i = 0; i < 3; i++) { - err = rtl8366_smi_read_reg(smi, - RTL8366RB_VLAN_MC_BASE(index) + i, - &data[i]); - if (err) - return err; - } - - vlanmc->vid = data[0] & RTL8366RB_VLAN_VID_MASK; - vlanmc->priority = (data[0] >> RTL8366RB_VLAN_PRIORITY_SHIFT) & - RTL8366RB_VLAN_PRIORITY_MASK; - vlanmc->untag = (data[1] >> RTL8366RB_VLAN_UNTAG_SHIFT) & - RTL8366RB_VLAN_UNTAG_MASK; - vlanmc->member = data[1] & RTL8366RB_VLAN_MEMBER_MASK; - vlanmc->fid = data[2] & RTL8366RB_VLAN_FID_MASK; - - return 0; -} - -static int rtl8366rb_set_vlan_mc(struct rtl8366_smi *smi, u32 index, - const struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[3]; - int err; - int i; - - if (index >= RTL8366RB_NUM_VLANS || - vlanmc->vid >= RTL8366RB_NUM_VIDS || - vlanmc->priority > RTL8366RB_PRIORITYMAX || - vlanmc->member > RTL8366RB_VLAN_MEMBER_MASK || - vlanmc->untag > RTL8366RB_VLAN_UNTAG_MASK || - vlanmc->fid > RTL8366RB_FIDMAX) - return -EINVAL; - - data[0] = (vlanmc->vid & RTL8366RB_VLAN_VID_MASK) | - ((vlanmc->priority & RTL8366RB_VLAN_PRIORITY_MASK) << - RTL8366RB_VLAN_PRIORITY_SHIFT); - data[1] = (vlanmc->member & RTL8366RB_VLAN_MEMBER_MASK) | - ((vlanmc->untag & RTL8366RB_VLAN_UNTAG_MASK) << - RTL8366RB_VLAN_UNTAG_SHIFT); - data[2] = vlanmc->fid & RTL8366RB_VLAN_FID_MASK; - - for (i = 0; i < 3; i++) { - err = rtl8366_smi_write_reg(smi, - RTL8366RB_VLAN_MC_BASE(index) + i, - data[i]); - if (err) - return err; - } - - return 0; -} - -static int rtl8366rb_get_mc_index(struct rtl8366_smi *smi, int port, int *val) -{ - u32 data; - int err; - - if (port >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - err = rtl8366_smi_read_reg(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), - &data); - if (err) - return err; - - *val = (data >> RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)) & - RTL8366RB_PORT_VLAN_CTRL_MASK; - - return 0; - -} - -static int rtl8366rb_set_mc_index(struct rtl8366_smi *smi, int port, int index) -{ - if (port >= RTL8366RB_NUM_PORTS || index >= RTL8366RB_NUM_VLANS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PORT_VLAN_CTRL_REG(port), - RTL8366RB_PORT_VLAN_CTRL_MASK << - RTL8366RB_PORT_VLAN_CTRL_SHIFT(port), - (index & RTL8366RB_PORT_VLAN_CTRL_MASK) << - RTL8366RB_PORT_VLAN_CTRL_SHIFT(port)); -} - -static int rtl8366rb_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) -{ - unsigned max = RTL8366RB_NUM_VLANS; - - if (smi->vlan4k_enabled) - max = RTL8366RB_NUM_VIDS - 1; - - if (vlan == 0 || vlan >= max) - return 0; - - return 1; -} - -static int rtl8366rb_enable_vlan(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_SGCR_EN_VLAN, - (enable) ? RTL8366RB_SGCR_EN_VLAN : 0); -} - -static int rtl8366rb_enable_vlan4k(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, - RTL8366RB_SGCR_EN_VLAN_4KTB, - (enable) ? RTL8366RB_SGCR_EN_VLAN_4KTB : 0); -} - -static int rtl8366rb_enable_port(struct rtl8366_smi *smi, int port, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, (1 << port), - (enable) ? 0 : (1 << port)); -} - -static int rtl8366rb_sw_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, - RTL8366RB_MIB_CTRL_GLOBAL_RESET); -} - -static int rtl8366rb_sw_get_blinkrate(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_LED_BLINKRATE_REG, &data); - - val->value.i = (data & (RTL8366RB_LED_BLINKRATE_MASK)); - - return 0; -} - -static int rtl8366rb_sw_set_blinkrate(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->value.i >= 6) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8366RB_LED_BLINKRATE_REG, - RTL8366RB_LED_BLINKRATE_MASK, - val->value.i); -} - -static int rtl8366rb_sw_get_learning_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_SSCR0, &data); - val->value.i = !data; - - return 0; -} - - -static int rtl8366rb_sw_set_learning_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 portmask = 0; - int err = 0; - - if (!val->value.i) - portmask = RTL8366RB_PORT_ALL; - - /* set learning for all ports */ - REG_WR(smi, RTL8366RB_SSCR0, portmask); - - /* set auto ageing for all ports */ - REG_WR(smi, RTL8366RB_SSCR1, portmask); - - return 0; -} - -static int rtl8366rb_sw_get_port_link(struct switch_dev *dev, - int port, - struct switch_port_link *link) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - u32 speed; - - if (port >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366RB_PORT_LINK_STATUS_BASE + (port / 2), - &data); - - if (port % 2) - data = data >> 8; - - link->link = !!(data & RTL8366RB_PORT_STATUS_LINK_MASK); - if (!link->link) - return 0; - - link->duplex = !!(data & RTL8366RB_PORT_STATUS_DUPLEX_MASK); - link->rx_flow = !!(data & RTL8366RB_PORT_STATUS_RXPAUSE_MASK); - link->tx_flow = !!(data & RTL8366RB_PORT_STATUS_TXPAUSE_MASK); - link->aneg = !!(data & RTL8366RB_PORT_STATUS_AN_MASK); - - speed = (data & RTL8366RB_PORT_STATUS_SPEED_MASK); - switch (speed) { - case 0: - link->speed = SWITCH_PORT_SPEED_10; - break; - case 1: - link->speed = SWITCH_PORT_SPEED_100; - break; - case 2: - link->speed = SWITCH_PORT_SPEED_1000; - break; - default: - link->speed = SWITCH_PORT_SPEED_UNKNOWN; - break; - } - - return 0; -} - -static int rtl8366rb_sw_set_port_led(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - u32 mask; - u32 reg; - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - if (val->port_vlan == RTL8366RB_PORT_NUM_CPU) { - reg = RTL8366RB_LED_BLINKRATE_REG; - mask = 0xF << 4; - data = val->value.i << 4; - } else { - reg = RTL8366RB_LED_CTRL_REG; - mask = 0xF << (val->port_vlan * 4), - data = val->value.i << (val->port_vlan * 4); - } - - return rtl8366_smi_rmwr(smi, reg, mask, data); -} - -static int rtl8366rb_sw_get_port_led(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - - if (val->port_vlan >= RTL8366RB_NUM_LEDGROUPS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366RB_LED_CTRL_REG, &data); - val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; - - return 0; -} - -static int rtl8366rb_sw_set_port_disable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 mask, data; - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - mask = 1 << val->port_vlan ; - if (val->value.i) - data = mask; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PECR, mask, data); -} - -static int rtl8366rb_sw_get_port_disable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366RB_PECR, &data); - if (data & (1 << val->port_vlan)) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_port_rate_in(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) - val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; - else - val->value.i = RTL8366RB_BDTH_REG_DEFAULT; - - return rtl8366_smi_rmwr(smi, RTL8366RB_IB_REG(val->port_vlan), - RTL8366RB_IB_BDTH_MASK | RTL8366RB_IB_PREIFG_MASK, - val->value.i | - (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_IB_PREIFG_OFFSET)); - -} - -static int rtl8366rb_sw_get_port_rate_in(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366RB_IB_REG(val->port_vlan), &data); - data &= RTL8366RB_IB_BDTH_MASK; - if (data < RTL8366RB_IB_BDTH_MASK) - data += 1; - - val->value.i = (int)data * RTL8366RB_BDTH_UNIT; - - return 0; -} - -static int rtl8366rb_sw_set_port_rate_out(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_rmwr(smi, RTL8366RB_EB_PREIFG_REG, - RTL8366RB_EB_PREIFG_MASK, - (RTL8366RB_QOS_DEFAULT_PREIFG << RTL8366RB_EB_PREIFG_OFFSET)); - - if (val->value.i > 0 && val->value.i < RTL8366RB_BDTH_SW_MAX) - val->value.i = (val->value.i - 1) / RTL8366RB_BDTH_UNIT; - else - val->value.i = RTL8366RB_BDTH_REG_DEFAULT; - - return rtl8366_smi_rmwr(smi, RTL8366RB_EB_REG(val->port_vlan), - RTL8366RB_EB_BDTH_MASK, val->value.i ); - -} - -static int rtl8366rb_sw_get_port_rate_out(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366RB_EB_REG(val->port_vlan), &data); - data &= RTL8366RB_EB_BDTH_MASK; - if (data < RTL8366RB_EB_BDTH_MASK) - data += 1; - - val->value.i = (int)data * RTL8366RB_BDTH_UNIT; - - return 0; -} - -static int rtl8366rb_sw_set_qos_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->value.i) - data = RTL8366RB_QOS_MASK; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_SGCR, RTL8366RB_QOS_MASK, data); -} - -static int rtl8366rb_sw_get_qos_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_SGCR, &data); - if (data & RTL8366RB_QOS_MASK) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->value.i) - data = RTL8366RB_PMCR_MIRROR_RX; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_RX, data); -} - -static int rtl8366rb_sw_get_mirror_rx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - if (data & RTL8366RB_PMCR_MIRROR_RX) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->value.i) - data = RTL8366RB_PMCR_MIRROR_TX; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_TX, data); -} - -static int rtl8366rb_sw_get_mirror_tx_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - if (data & RTL8366RB_PMCR_MIRROR_TX) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_monitor_isolation_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->value.i) - data = RTL8366RB_PMCR_MIRROR_ISO; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_ISO, data); -} - -static int rtl8366rb_sw_get_monitor_isolation_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - if (data & RTL8366RB_PMCR_MIRROR_ISO) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_mirror_pause_frames_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - if (val->value.i) - data = RTL8366RB_PMCR_MIRROR_SPC; - else - data = 0; - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MIRROR_SPC, data); -} - -static int rtl8366rb_sw_get_mirror_pause_frames_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - if (data & RTL8366RB_PMCR_MIRROR_SPC) - val->value.i = 1; - else - val->value.i = 0; - - return 0; -} - -static int rtl8366rb_sw_set_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - data = RTL8366RB_PMCR_MONITOR_PORT(val->value.i); - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_MONITOR_PORT_MASK, data); -} - -static int rtl8366rb_sw_get_mirror_monitor_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - val->value.i = (data & RTL8366RB_PMCR_MONITOR_PORT_MASK) >> 4; - - return 0; -} - -static int rtl8366rb_sw_set_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - data = RTL8366RB_PMCR_SOURCE_PORT(val->value.i); - - return rtl8366_smi_rmwr(smi, RTL8366RB_PMCR, RTL8366RB_PMCR_SOURCE_PORT_MASK, data); -} - -static int rtl8366rb_sw_get_mirror_source_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366RB_PMCR, &data); - val->value.i = data & RTL8366RB_PMCR_SOURCE_PORT_MASK; - - return 0; -} - -static int rtl8366rb_sw_reset_port_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->port_vlan >= RTL8366RB_NUM_PORTS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8366RB_MIB_CTRL_REG, 0, - RTL8366RB_MIB_CTRL_PORT_RESET(val->port_vlan)); -} - -static int rtl8366rb_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats) -{ - return (rtl8366_sw_get_port_stats(dev, port, stats, - RTL8366RB_MIB_TXB_ID, RTL8366RB_MIB_RXB_ID)); -} - -static struct switch_attr rtl8366rb_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_learning", - .description = "Enable learning, enable aging", - .set = rtl8366rb_sw_set_learning_enable, - .get = rtl8366rb_sw_get_learning_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan4k", - .description = "Enable VLAN 4K mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 2 - }, { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = rtl8366rb_sw_reset_mibs, - }, { - .type = SWITCH_TYPE_INT, - .name = "blinkrate", - .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," - " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", - .set = rtl8366rb_sw_set_blinkrate, - .get = rtl8366rb_sw_get_blinkrate, - .max = 5 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_qos", - .description = "Enable QOS", - .set = rtl8366rb_sw_set_qos_enable, - .get = rtl8366rb_sw_get_qos_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_rx", - .description = "Enable mirroring of RX packets", - .set = rtl8366rb_sw_set_mirror_rx_enable, - .get = rtl8366rb_sw_get_mirror_rx_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_tx", - .description = "Enable mirroring of TX packets", - .set = rtl8366rb_sw_set_mirror_tx_enable, - .get = rtl8366rb_sw_get_mirror_tx_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_monitor_isolation", - .description = "Enable isolation of monitor port (TX packets will be dropped)", - .set = rtl8366rb_sw_set_monitor_isolation_enable, - .get = rtl8366rb_sw_get_monitor_isolation_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_mirror_pause_frames", - .description = "Enable mirroring of RX pause frames", - .set = rtl8366rb_sw_set_mirror_pause_frames_enable, - .get = rtl8366rb_sw_get_mirror_pause_frames_enable, - .max = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "mirror_monitor_port", - .description = "Mirror monitor port", - .set = rtl8366rb_sw_set_mirror_monitor_port, - .get = rtl8366rb_sw_get_mirror_monitor_port, - .max = 5 - }, { - .type = SWITCH_TYPE_INT, - .name = "mirror_source_port", - .description = "Mirror source port", - .set = rtl8366rb_sw_set_mirror_source_port, - .get = rtl8366rb_sw_get_mirror_source_port, - .max = 5 - }, -}; - -static struct switch_attr rtl8366rb_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = rtl8366rb_sw_reset_port_mibs, - }, { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get MIB counters for port", - .max = 33, - .set = NULL, - .get = rtl8366_sw_get_port_mib, - }, { - .type = SWITCH_TYPE_INT, - .name = "led", - .description = "Get/Set port group (0 - 3) led mode (0 - 15)", - .max = 15, - .set = rtl8366rb_sw_set_port_led, - .get = rtl8366rb_sw_get_port_led, - }, { - .type = SWITCH_TYPE_INT, - .name = "disable", - .description = "Get/Set port state (enabled or disabled)", - .max = 1, - .set = rtl8366rb_sw_set_port_disable, - .get = rtl8366rb_sw_get_port_disable, - }, { - .type = SWITCH_TYPE_INT, - .name = "rate_in", - .description = "Get/Set port ingress (incoming) bandwidth limit in kbps", - .max = RTL8366RB_BDTH_SW_MAX, - .set = rtl8366rb_sw_set_port_rate_in, - .get = rtl8366rb_sw_get_port_rate_in, - }, { - .type = SWITCH_TYPE_INT, - .name = "rate_out", - .description = "Get/Set port egress (outgoing) bandwidth limit in kbps", - .max = RTL8366RB_BDTH_SW_MAX, - .set = rtl8366rb_sw_set_port_rate_out, - .get = rtl8366rb_sw_get_port_rate_out, - }, -}; - -static struct switch_attr rtl8366rb_vlan[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "info", - .description = "Get vlan information", - .max = 1, - .set = NULL, - .get = rtl8366_sw_get_vlan_info, - }, { - .type = SWITCH_TYPE_INT, - .name = "fid", - .description = "Get/Set vlan FID", - .max = RTL8366RB_FIDMAX, - .set = rtl8366_sw_set_vlan_fid, - .get = rtl8366_sw_get_vlan_fid, - }, -}; - -static const struct switch_dev_ops rtl8366_ops = { - .attr_global = { - .attr = rtl8366rb_globals, - .n_attr = ARRAY_SIZE(rtl8366rb_globals), - }, - .attr_port = { - .attr = rtl8366rb_port, - .n_attr = ARRAY_SIZE(rtl8366rb_port), - }, - .attr_vlan = { - .attr = rtl8366rb_vlan, - .n_attr = ARRAY_SIZE(rtl8366rb_vlan), - }, - - .get_vlan_ports = rtl8366_sw_get_vlan_ports, - .set_vlan_ports = rtl8366_sw_set_vlan_ports, - .get_port_pvid = rtl8366_sw_get_port_pvid, - .set_port_pvid = rtl8366_sw_set_port_pvid, - .reset_switch = rtl8366_sw_reset_switch, - .get_port_link = rtl8366rb_sw_get_port_link, - .get_port_stats = rtl8366rb_sw_get_port_stats, -}; - -static int rtl8366rb_switch_init(struct rtl8366_smi *smi) -{ - struct switch_dev *dev = &smi->sw_dev; - int err; - - dev->name = "RTL8366RB"; - dev->cpu_port = RTL8366RB_PORT_NUM_CPU; - dev->ports = RTL8366RB_NUM_PORTS; - dev->vlans = RTL8366RB_NUM_VIDS; - dev->ops = &rtl8366_ops; - dev->alias = dev_name(smi->parent); - - err = register_switch(dev, NULL); - if (err) - dev_err(smi->parent, "switch registration failed\n"); - - return err; -} - -static void rtl8366rb_switch_cleanup(struct rtl8366_smi *smi) -{ - unregister_switch(&smi->sw_dev); -} - -static int rtl8366rb_mii_read(struct mii_bus *bus, int addr, int reg) -{ - struct rtl8366_smi *smi = bus->priv; - u32 val = 0; - int err; - - err = rtl8366rb_read_phy_reg(smi, addr, 0, reg, &val); - if (err) - return 0xffff; - - return val; -} - -static int rtl8366rb_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) -{ - struct rtl8366_smi *smi = bus->priv; - u32 t; - int err; - - err = rtl8366rb_write_phy_reg(smi, addr, 0, reg, val); - /* flush write */ - (void) rtl8366rb_read_phy_reg(smi, addr, 0, reg, &t); - - return err; -} - -static int rtl8366rb_detect(struct rtl8366_smi *smi) -{ - u32 chip_id = 0; - u32 chip_ver = 0; - int ret; - - ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_ID_REG, &chip_id); - if (ret) { - dev_err(smi->parent, "unable to read chip id\n"); - return ret; - } - - switch (chip_id) { - case RTL8366RB_CHIP_ID_8366: - break; - default: - dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); - return -ENODEV; - } - - ret = rtl8366_smi_read_reg(smi, RTL8366RB_CHIP_VERSION_CTRL_REG, - &chip_ver); - if (ret) { - dev_err(smi->parent, "unable to read chip version\n"); - return ret; - } - - dev_info(smi->parent, "RTL%04x ver. %u chip found\n", - chip_id, chip_ver & RTL8366RB_CHIP_VERSION_MASK); - - return 0; -} - -static struct rtl8366_smi_ops rtl8366rb_smi_ops = { - .detect = rtl8366rb_detect, - .reset_chip = rtl8366rb_reset_chip, - .setup = rtl8366rb_setup, - - .mii_read = rtl8366rb_mii_read, - .mii_write = rtl8366rb_mii_write, - - .get_vlan_mc = rtl8366rb_get_vlan_mc, - .set_vlan_mc = rtl8366rb_set_vlan_mc, - .get_vlan_4k = rtl8366rb_get_vlan_4k, - .set_vlan_4k = rtl8366rb_set_vlan_4k, - .get_mc_index = rtl8366rb_get_mc_index, - .set_mc_index = rtl8366rb_set_mc_index, - .get_mib_counter = rtl8366rb_get_mib_counter, - .is_vlan_valid = rtl8366rb_is_vlan_valid, - .enable_vlan = rtl8366rb_enable_vlan, - .enable_vlan4k = rtl8366rb_enable_vlan4k, - .enable_port = rtl8366rb_enable_port, -}; - -static int rtl8366rb_probe(struct platform_device *pdev) -{ - static int rtl8366_smi_version_printed; - struct rtl8366_smi *smi; - int err; - - if (!rtl8366_smi_version_printed++) - printk(KERN_NOTICE RTL8366RB_DRIVER_DESC - " version " RTL8366RB_DRIVER_VER"\n"); - - smi = rtl8366_smi_probe(pdev); - if (!smi) - return -ENODEV; - - smi->clk_delay = 10; - smi->cmd_read = 0xa9; - smi->cmd_write = 0xa8; - smi->ops = &rtl8366rb_smi_ops; - smi->cpu_port = RTL8366RB_PORT_NUM_CPU; - smi->num_ports = RTL8366RB_NUM_PORTS; - smi->num_vlan_mc = RTL8366RB_NUM_VLANS; - smi->mib_counters = rtl8366rb_mib_counters; - smi->num_mib_counters = ARRAY_SIZE(rtl8366rb_mib_counters); - - err = rtl8366_smi_init(smi); - if (err) - goto err_free_smi; - - platform_set_drvdata(pdev, smi); - - err = rtl8366rb_switch_init(smi); - if (err) - goto err_clear_drvdata; - - return 0; - - err_clear_drvdata: - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - err_free_smi: - kfree(smi); - return err; -} - -static int rtl8366rb_remove(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) { - rtl8366rb_switch_cleanup(smi); - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - kfree(smi); - } - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id rtl8366rb_match[] = { - { .compatible = "realtek,rtl8366rb" }, - {}, -}; -MODULE_DEVICE_TABLE(of, rtl8366rb_match); -#endif - -static struct platform_driver rtl8366rb_driver = { - .driver = { - .name = RTL8366RB_DRIVER_NAME, - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(rtl8366rb_match), - }, - .probe = rtl8366rb_probe, - .remove = rtl8366rb_remove, -}; - -static int __init rtl8366rb_module_init(void) -{ - return platform_driver_register(&rtl8366rb_driver); -} -module_init(rtl8366rb_module_init); - -static void __exit rtl8366rb_module_exit(void) -{ - platform_driver_unregister(&rtl8366rb_driver); -} -module_exit(rtl8366rb_module_exit); - -MODULE_DESCRIPTION(RTL8366RB_DRIVER_DESC); -MODULE_VERSION(RTL8366RB_DRIVER_VER); -MODULE_AUTHOR("Gabor Juhos "); -MODULE_AUTHOR("Antti Seppälä "); -MODULE_AUTHOR("Roman Yeryomin "); -MODULE_AUTHOR("Colin Leitner "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8366s.c b/target/linux/generic/files/drivers/net/phy/rtl8366s.c deleted file mode 100644 index 3f458f9dec..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8366s.c +++ /dev/null @@ -1,1320 +0,0 @@ -/* - * Platform driver for the Realtek RTL8366S ethernet switch - * - * Copyright (C) 2009-2010 Gabor Juhos - * Copyright (C) 2010 Antti Seppälä - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rtl8366_smi.h" - -#define RTL8366S_DRIVER_DESC "Realtek RTL8366S ethernet switch driver" -#define RTL8366S_DRIVER_VER "0.2.2" - -#define RTL8366S_PHY_NO_MAX 4 -#define RTL8366S_PHY_PAGE_MAX 7 -#define RTL8366S_PHY_ADDR_MAX 31 - -/* Switch Global Configuration register */ -#define RTL8366S_SGCR 0x0000 -#define RTL8366S_SGCR_EN_BC_STORM_CTRL BIT(0) -#define RTL8366S_SGCR_MAX_LENGTH(_x) (_x << 4) -#define RTL8366S_SGCR_MAX_LENGTH_MASK RTL8366S_SGCR_MAX_LENGTH(0x3) -#define RTL8366S_SGCR_MAX_LENGTH_1522 RTL8366S_SGCR_MAX_LENGTH(0x0) -#define RTL8366S_SGCR_MAX_LENGTH_1536 RTL8366S_SGCR_MAX_LENGTH(0x1) -#define RTL8366S_SGCR_MAX_LENGTH_1552 RTL8366S_SGCR_MAX_LENGTH(0x2) -#define RTL8366S_SGCR_MAX_LENGTH_16000 RTL8366S_SGCR_MAX_LENGTH(0x3) -#define RTL8366S_SGCR_EN_VLAN BIT(13) - -/* Port Enable Control register */ -#define RTL8366S_PECR 0x0001 - -/* Green Ethernet Feature (based on GPL_BELKIN_F5D8235-4_v1000 v1.01.24) */ -#define RTL8366S_GREEN_ETHERNET_CTRL_REG 0x000a -#define RTL8366S_GREEN_ETHERNET_CTRL_MASK 0x0018 -#define RTL8366S_GREEN_ETHERNET_TX_BIT (1 << 3) -#define RTL8366S_GREEN_ETHERNET_RX_BIT (1 << 4) - -/* Switch Security Control registers */ -#define RTL8366S_SSCR0 0x0002 -#define RTL8366S_SSCR1 0x0003 -#define RTL8366S_SSCR2 0x0004 -#define RTL8366S_SSCR2_DROP_UNKNOWN_DA BIT(0) - -#define RTL8366S_RESET_CTRL_REG 0x0100 -#define RTL8366S_CHIP_CTRL_RESET_HW 1 -#define RTL8366S_CHIP_CTRL_RESET_SW (1 << 1) - -#define RTL8366S_CHIP_VERSION_CTRL_REG 0x0104 -#define RTL8366S_CHIP_VERSION_MASK 0xf -#define RTL8366S_CHIP_ID_REG 0x0105 -#define RTL8366S_CHIP_ID_8366 0x8366 - -/* PHY registers control */ -#define RTL8366S_PHY_ACCESS_CTRL_REG 0x8028 -#define RTL8366S_PHY_ACCESS_DATA_REG 0x8029 - -#define RTL8366S_PHY_CTRL_READ 1 -#define RTL8366S_PHY_CTRL_WRITE 0 - -#define RTL8366S_PHY_REG_MASK 0x1f -#define RTL8366S_PHY_PAGE_OFFSET 5 -#define RTL8366S_PHY_PAGE_MASK (0x7 << 5) -#define RTL8366S_PHY_NO_OFFSET 9 -#define RTL8366S_PHY_NO_MASK (0x1f << 9) - -/* Green Ethernet Feature for PHY ports */ -#define RTL8366S_PHY_POWER_SAVING_CTRL_REG 12 -#define RTL8366S_PHY_POWER_SAVING_MASK 0x1000 - -/* LED control registers */ -#define RTL8366S_LED_BLINKRATE_REG 0x0420 -#define RTL8366S_LED_BLINKRATE_BIT 0 -#define RTL8366S_LED_BLINKRATE_MASK 0x0007 - -#define RTL8366S_LED_CTRL_REG 0x0421 -#define RTL8366S_LED_0_1_CTRL_REG 0x0422 -#define RTL8366S_LED_2_3_CTRL_REG 0x0423 - -#define RTL8366S_MIB_COUNT 33 -#define RTL8366S_GLOBAL_MIB_COUNT 1 -#define RTL8366S_MIB_COUNTER_PORT_OFFSET 0x0040 -#define RTL8366S_MIB_COUNTER_BASE 0x1000 -#define RTL8366S_MIB_COUNTER_PORT_OFFSET2 0x0008 -#define RTL8366S_MIB_COUNTER_BASE2 0x1180 -#define RTL8366S_MIB_CTRL_REG 0x11F0 -#define RTL8366S_MIB_CTRL_USER_MASK 0x01FF -#define RTL8366S_MIB_CTRL_BUSY_MASK 0x0001 -#define RTL8366S_MIB_CTRL_RESET_MASK 0x0002 - -#define RTL8366S_MIB_CTRL_GLOBAL_RESET_MASK 0x0004 -#define RTL8366S_MIB_CTRL_PORT_RESET_BIT 0x0003 -#define RTL8366S_MIB_CTRL_PORT_RESET_MASK 0x01FC - - -#define RTL8366S_PORT_VLAN_CTRL_BASE 0x0058 -#define RTL8366S_PORT_VLAN_CTRL_REG(_p) \ - (RTL8366S_PORT_VLAN_CTRL_BASE + (_p) / 4) -#define RTL8366S_PORT_VLAN_CTRL_MASK 0xf -#define RTL8366S_PORT_VLAN_CTRL_SHIFT(_p) (4 * ((_p) % 4)) - - -#define RTL8366S_VLAN_TABLE_READ_BASE 0x018B -#define RTL8366S_VLAN_TABLE_WRITE_BASE 0x0185 - -#define RTL8366S_VLAN_TB_CTRL_REG 0x010F - -#define RTL8366S_TABLE_ACCESS_CTRL_REG 0x0180 -#define RTL8366S_TABLE_VLAN_READ_CTRL 0x0E01 -#define RTL8366S_TABLE_VLAN_WRITE_CTRL 0x0F01 - -#define RTL8366S_VLAN_MC_BASE(_x) (0x0016 + (_x) * 2) - -#define RTL8366S_VLAN_MEMBERINGRESS_REG 0x0379 - -#define RTL8366S_PORT_LINK_STATUS_BASE 0x0060 -#define RTL8366S_PORT_STATUS_SPEED_MASK 0x0003 -#define RTL8366S_PORT_STATUS_DUPLEX_MASK 0x0004 -#define RTL8366S_PORT_STATUS_LINK_MASK 0x0010 -#define RTL8366S_PORT_STATUS_TXPAUSE_MASK 0x0020 -#define RTL8366S_PORT_STATUS_RXPAUSE_MASK 0x0040 -#define RTL8366S_PORT_STATUS_AN_MASK 0x0080 - - -#define RTL8366S_PORT_NUM_CPU 5 -#define RTL8366S_NUM_PORTS 6 -#define RTL8366S_NUM_VLANS 16 -#define RTL8366S_NUM_LEDGROUPS 4 -#define RTL8366S_NUM_VIDS 4096 -#define RTL8366S_PRIORITYMAX 7 -#define RTL8366S_FIDMAX 7 - - -#define RTL8366S_PORT_1 (1 << 0) /* In userspace port 0 */ -#define RTL8366S_PORT_2 (1 << 1) /* In userspace port 1 */ -#define RTL8366S_PORT_3 (1 << 2) /* In userspace port 2 */ -#define RTL8366S_PORT_4 (1 << 3) /* In userspace port 3 */ - -#define RTL8366S_PORT_UNKNOWN (1 << 4) /* No known connection */ -#define RTL8366S_PORT_CPU (1 << 5) /* CPU port */ - -#define RTL8366S_PORT_ALL (RTL8366S_PORT_1 | \ - RTL8366S_PORT_2 | \ - RTL8366S_PORT_3 | \ - RTL8366S_PORT_4 | \ - RTL8366S_PORT_UNKNOWN | \ - RTL8366S_PORT_CPU) - -#define RTL8366S_PORT_ALL_BUT_CPU (RTL8366S_PORT_1 | \ - RTL8366S_PORT_2 | \ - RTL8366S_PORT_3 | \ - RTL8366S_PORT_4 | \ - RTL8366S_PORT_UNKNOWN) - -#define RTL8366S_PORT_ALL_EXTERNAL (RTL8366S_PORT_1 | \ - RTL8366S_PORT_2 | \ - RTL8366S_PORT_3 | \ - RTL8366S_PORT_4) - -#define RTL8366S_PORT_ALL_INTERNAL (RTL8366S_PORT_UNKNOWN | \ - RTL8366S_PORT_CPU) - -#define RTL8366S_VLAN_VID_MASK 0xfff -#define RTL8366S_VLAN_PRIORITY_SHIFT 12 -#define RTL8366S_VLAN_PRIORITY_MASK 0x7 -#define RTL8366S_VLAN_MEMBER_MASK 0x3f -#define RTL8366S_VLAN_UNTAG_SHIFT 6 -#define RTL8366S_VLAN_UNTAG_MASK 0x3f -#define RTL8366S_VLAN_FID_SHIFT 12 -#define RTL8366S_VLAN_FID_MASK 0x7 - -#define RTL8366S_MIB_RXB_ID 0 /* IfInOctets */ -#define RTL8366S_MIB_TXB_ID 20 /* IfOutOctets */ - -static struct rtl8366_mib_counter rtl8366s_mib_counters[] = { - { 0, 0, 4, "IfInOctets" }, - { 0, 4, 4, "EtherStatsOctets" }, - { 0, 8, 2, "EtherStatsUnderSizePkts" }, - { 0, 10, 2, "EtherFragments" }, - { 0, 12, 2, "EtherStatsPkts64Octets" }, - { 0, 14, 2, "EtherStatsPkts65to127Octets" }, - { 0, 16, 2, "EtherStatsPkts128to255Octets" }, - { 0, 18, 2, "EtherStatsPkts256to511Octets" }, - { 0, 20, 2, "EtherStatsPkts512to1023Octets" }, - { 0, 22, 2, "EtherStatsPkts1024to1518Octets" }, - { 0, 24, 2, "EtherOversizeStats" }, - { 0, 26, 2, "EtherStatsJabbers" }, - { 0, 28, 2, "IfInUcastPkts" }, - { 0, 30, 2, "EtherStatsMulticastPkts" }, - { 0, 32, 2, "EtherStatsBroadcastPkts" }, - { 0, 34, 2, "EtherStatsDropEvents" }, - { 0, 36, 2, "Dot3StatsFCSErrors" }, - { 0, 38, 2, "Dot3StatsSymbolErrors" }, - { 0, 40, 2, "Dot3InPauseFrames" }, - { 0, 42, 2, "Dot3ControlInUnknownOpcodes" }, - { 0, 44, 4, "IfOutOctets" }, - { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, - { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, - { 0, 52, 2, "Dot3sDeferredTransmissions" }, - { 0, 54, 2, "Dot3StatsLateCollisions" }, - { 0, 56, 2, "EtherStatsCollisions" }, - { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, - { 0, 60, 2, "Dot3OutPauseFrames" }, - { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, - - /* - * The following counters are accessible at a different - * base address. - */ - { 1, 0, 2, "Dot1dTpPortInDiscards" }, - { 1, 2, 2, "IfOutUcastPkts" }, - { 1, 4, 2, "IfOutMulticastPkts" }, - { 1, 6, 2, "IfOutBroadcastPkts" }, -}; - -#define REG_WR(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_write_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_RMW(_smi, _reg, _mask, _val) \ - do { \ - err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ - if (err) \ - return err; \ - } while (0) - -static int rtl8366s_reset_chip(struct rtl8366_smi *smi) -{ - int timeout = 10; - u32 data; - - rtl8366_smi_write_reg_noack(smi, RTL8366S_RESET_CTRL_REG, - RTL8366S_CHIP_CTRL_RESET_HW); - do { - msleep(1); - if (rtl8366_smi_read_reg(smi, RTL8366S_RESET_CTRL_REG, &data)) - return -EIO; - - if (!(data & RTL8366S_CHIP_CTRL_RESET_HW)) - break; - } while (--timeout); - - if (!timeout) { - printk("Timeout waiting for the switch to reset\n"); - return -EIO; - } - - return 0; -} - -static int rtl8366s_read_phy_reg(struct rtl8366_smi *smi, - u32 phy_no, u32 page, u32 addr, u32 *data) -{ - u32 reg; - int ret; - - if (phy_no > RTL8366S_PHY_NO_MAX) - return -EINVAL; - - if (page > RTL8366S_PHY_PAGE_MAX) - return -EINVAL; - - if (addr > RTL8366S_PHY_ADDR_MAX) - return -EINVAL; - - ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, - RTL8366S_PHY_CTRL_READ); - if (ret) - return ret; - - reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | - ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | - (addr & RTL8366S_PHY_REG_MASK); - - ret = rtl8366_smi_write_reg(smi, reg, 0); - if (ret) - return ret; - - ret = rtl8366_smi_read_reg(smi, RTL8366S_PHY_ACCESS_DATA_REG, data); - if (ret) - return ret; - - return 0; -} - -static int rtl8366s_write_phy_reg(struct rtl8366_smi *smi, - u32 phy_no, u32 page, u32 addr, u32 data) -{ - u32 reg; - int ret; - - if (phy_no > RTL8366S_PHY_NO_MAX) - return -EINVAL; - - if (page > RTL8366S_PHY_PAGE_MAX) - return -EINVAL; - - if (addr > RTL8366S_PHY_ADDR_MAX) - return -EINVAL; - - ret = rtl8366_smi_write_reg(smi, RTL8366S_PHY_ACCESS_CTRL_REG, - RTL8366S_PHY_CTRL_WRITE); - if (ret) - return ret; - - reg = 0x8000 | (1 << (phy_no + RTL8366S_PHY_NO_OFFSET)) | - ((page << RTL8366S_PHY_PAGE_OFFSET) & RTL8366S_PHY_PAGE_MASK) | - (addr & RTL8366S_PHY_REG_MASK); - - ret = rtl8366_smi_write_reg(smi, reg, data); - if (ret) - return ret; - - return 0; -} - -static int rtl8366s_set_green_port(struct rtl8366_smi *smi, int port, int enable) -{ - int err; - u32 phyData; - - if (port >= RTL8366S_NUM_PORTS) - return -EINVAL; - - err = rtl8366s_read_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); - if (err) - return err; - - if (enable) - phyData |= RTL8366S_PHY_POWER_SAVING_MASK; - else - phyData &= ~RTL8366S_PHY_POWER_SAVING_MASK; - - err = rtl8366s_write_phy_reg(smi, port, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, phyData); - if (err) - return err; - - return 0; -} - -static int rtl8366s_set_green(struct rtl8366_smi *smi, int enable) -{ - int err; - unsigned i; - u32 data = 0; - - if (!enable) { - for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { - rtl8366s_set_green_port(smi, i, 0); - } - } - - if (enable) - data = (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT); - - REG_RMW(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, RTL8366S_GREEN_ETHERNET_CTRL_MASK, data); - - return 0; -} - -static int rtl8366s_setup(struct rtl8366_smi *smi) -{ - struct rtl8366_platform_data *pdata; - int err; - unsigned i; -#ifdef CONFIG_OF - struct device_node *np; - unsigned num_initvals; - const __be32 *paddr; -#endif - - pdata = smi->parent->platform_data; - if (pdata && pdata->num_initvals && pdata->initvals) { - dev_info(smi->parent, "applying initvals\n"); - for (i = 0; i < pdata->num_initvals; i++) - REG_WR(smi, pdata->initvals[i].reg, - pdata->initvals[i].val); - } - -#ifdef CONFIG_OF - np = smi->parent->of_node; - - paddr = of_get_property(np, "realtek,initvals", &num_initvals); - if (paddr) { - dev_info(smi->parent, "applying initvals from DTS\n"); - - if (num_initvals < (2 * sizeof(*paddr))) - return -EINVAL; - - num_initvals /= sizeof(*paddr); - - for (i = 0; i < num_initvals - 1; i += 2) { - u32 reg = be32_to_cpup(paddr + i); - u32 val = be32_to_cpup(paddr + i + 1); - - REG_WR(smi, reg, val); - } - } - - if (of_property_read_bool(np, "realtek,green-ethernet-features")) { - dev_info(smi->parent, "activating Green Ethernet features\n"); - - err = rtl8366s_set_green(smi, 1); - if (err) - return err; - - for (i = 0; i <= RTL8366S_PHY_NO_MAX; i++) { - err = rtl8366s_set_green_port(smi, i, 1); - if (err) - return err; - } - } -#endif - - /* set maximum packet length to 1536 bytes */ - REG_RMW(smi, RTL8366S_SGCR, RTL8366S_SGCR_MAX_LENGTH_MASK, - RTL8366S_SGCR_MAX_LENGTH_1536); - - /* enable learning for all ports */ - REG_WR(smi, RTL8366S_SSCR0, 0); - - /* enable auto ageing for all ports */ - REG_WR(smi, RTL8366S_SSCR1, 0); - - /* - * discard VLAN tagged packets if the port is not a member of - * the VLAN with which the packets is associated. - */ - REG_WR(smi, RTL8366S_VLAN_MEMBERINGRESS_REG, RTL8366S_PORT_ALL); - - /* don't drop packets whose DA has not been learned */ - REG_RMW(smi, RTL8366S_SSCR2, RTL8366S_SSCR2_DROP_UNKNOWN_DA, 0); - - return 0; -} - -static int rtl8366_get_mib_counter(struct rtl8366_smi *smi, int counter, - int port, unsigned long long *val) -{ - int i; - int err; - u32 addr, data; - u64 mibvalue; - - if (port > RTL8366S_NUM_PORTS || counter >= RTL8366S_MIB_COUNT) - return -EINVAL; - - switch (rtl8366s_mib_counters[counter].base) { - case 0: - addr = RTL8366S_MIB_COUNTER_BASE + - RTL8366S_MIB_COUNTER_PORT_OFFSET * port; - break; - - case 1: - addr = RTL8366S_MIB_COUNTER_BASE2 + - RTL8366S_MIB_COUNTER_PORT_OFFSET2 * port; - break; - - default: - return -EINVAL; - } - - addr += rtl8366s_mib_counters[counter].offset; - - /* - * Writing access counter address first - * then ASIC will prepare 64bits counter wait for being retrived - */ - data = 0; /* writing data will be discard by ASIC */ - err = rtl8366_smi_write_reg(smi, addr, data); - if (err) - return err; - - /* read MIB control register */ - err = rtl8366_smi_read_reg(smi, RTL8366S_MIB_CTRL_REG, &data); - if (err) - return err; - - if (data & RTL8366S_MIB_CTRL_BUSY_MASK) - return -EBUSY; - - if (data & RTL8366S_MIB_CTRL_RESET_MASK) - return -EIO; - - mibvalue = 0; - for (i = rtl8366s_mib_counters[counter].length; i > 0; i--) { - err = rtl8366_smi_read_reg(smi, addr + (i - 1), &data); - if (err) - return err; - - mibvalue = (mibvalue << 16) | (data & 0xFFFF); - } - - *val = mibvalue; - return 0; -} - -static int rtl8366s_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, - struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[2]; - int err; - int i; - - memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); - - if (vid >= RTL8366S_NUM_VIDS) - return -EINVAL; - - /* write VID */ - err = rtl8366_smi_write_reg(smi, RTL8366S_VLAN_TABLE_WRITE_BASE, - vid & RTL8366S_VLAN_VID_MASK); - if (err) - return err; - - /* write table access control word */ - err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, - RTL8366S_TABLE_VLAN_READ_CTRL); - if (err) - return err; - - for (i = 0; i < 2; i++) { - err = rtl8366_smi_read_reg(smi, - RTL8366S_VLAN_TABLE_READ_BASE + i, - &data[i]); - if (err) - return err; - } - - vlan4k->vid = vid; - vlan4k->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & - RTL8366S_VLAN_UNTAG_MASK; - vlan4k->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; - vlan4k->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & - RTL8366S_VLAN_FID_MASK; - - return 0; -} - -static int rtl8366s_set_vlan_4k(struct rtl8366_smi *smi, - const struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[2]; - int err; - int i; - - if (vlan4k->vid >= RTL8366S_NUM_VIDS || - vlan4k->member > RTL8366S_VLAN_MEMBER_MASK || - vlan4k->untag > RTL8366S_VLAN_UNTAG_MASK || - vlan4k->fid > RTL8366S_FIDMAX) - return -EINVAL; - - data[0] = vlan4k->vid & RTL8366S_VLAN_VID_MASK; - data[1] = (vlan4k->member & RTL8366S_VLAN_MEMBER_MASK) | - ((vlan4k->untag & RTL8366S_VLAN_UNTAG_MASK) << - RTL8366S_VLAN_UNTAG_SHIFT) | - ((vlan4k->fid & RTL8366S_VLAN_FID_MASK) << - RTL8366S_VLAN_FID_SHIFT); - - for (i = 0; i < 2; i++) { - err = rtl8366_smi_write_reg(smi, - RTL8366S_VLAN_TABLE_WRITE_BASE + i, - data[i]); - if (err) - return err; - } - - /* write table access control word */ - err = rtl8366_smi_write_reg(smi, RTL8366S_TABLE_ACCESS_CTRL_REG, - RTL8366S_TABLE_VLAN_WRITE_CTRL); - - return err; -} - -static int rtl8366s_get_vlan_mc(struct rtl8366_smi *smi, u32 index, - struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[2]; - int err; - int i; - - memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); - - if (index >= RTL8366S_NUM_VLANS) - return -EINVAL; - - for (i = 0; i < 2; i++) { - err = rtl8366_smi_read_reg(smi, - RTL8366S_VLAN_MC_BASE(index) + i, - &data[i]); - if (err) - return err; - } - - vlanmc->vid = data[0] & RTL8366S_VLAN_VID_MASK; - vlanmc->priority = (data[0] >> RTL8366S_VLAN_PRIORITY_SHIFT) & - RTL8366S_VLAN_PRIORITY_MASK; - vlanmc->untag = (data[1] >> RTL8366S_VLAN_UNTAG_SHIFT) & - RTL8366S_VLAN_UNTAG_MASK; - vlanmc->member = data[1] & RTL8366S_VLAN_MEMBER_MASK; - vlanmc->fid = (data[1] >> RTL8366S_VLAN_FID_SHIFT) & - RTL8366S_VLAN_FID_MASK; - - return 0; -} - -static int rtl8366s_set_vlan_mc(struct rtl8366_smi *smi, u32 index, - const struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[2]; - int err; - int i; - - if (index >= RTL8366S_NUM_VLANS || - vlanmc->vid >= RTL8366S_NUM_VIDS || - vlanmc->priority > RTL8366S_PRIORITYMAX || - vlanmc->member > RTL8366S_VLAN_MEMBER_MASK || - vlanmc->untag > RTL8366S_VLAN_UNTAG_MASK || - vlanmc->fid > RTL8366S_FIDMAX) - return -EINVAL; - - data[0] = (vlanmc->vid & RTL8366S_VLAN_VID_MASK) | - ((vlanmc->priority & RTL8366S_VLAN_PRIORITY_MASK) << - RTL8366S_VLAN_PRIORITY_SHIFT); - data[1] = (vlanmc->member & RTL8366S_VLAN_MEMBER_MASK) | - ((vlanmc->untag & RTL8366S_VLAN_UNTAG_MASK) << - RTL8366S_VLAN_UNTAG_SHIFT) | - ((vlanmc->fid & RTL8366S_VLAN_FID_MASK) << - RTL8366S_VLAN_FID_SHIFT); - - for (i = 0; i < 2; i++) { - err = rtl8366_smi_write_reg(smi, - RTL8366S_VLAN_MC_BASE(index) + i, - data[i]); - if (err) - return err; - } - - return 0; -} - -static int rtl8366s_get_mc_index(struct rtl8366_smi *smi, int port, int *val) -{ - u32 data; - int err; - - if (port >= RTL8366S_NUM_PORTS) - return -EINVAL; - - err = rtl8366_smi_read_reg(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), - &data); - if (err) - return err; - - *val = (data >> RTL8366S_PORT_VLAN_CTRL_SHIFT(port)) & - RTL8366S_PORT_VLAN_CTRL_MASK; - - return 0; -} - -static int rtl8366s_set_mc_index(struct rtl8366_smi *smi, int port, int index) -{ - if (port >= RTL8366S_NUM_PORTS || index >= RTL8366S_NUM_VLANS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8366S_PORT_VLAN_CTRL_REG(port), - RTL8366S_PORT_VLAN_CTRL_MASK << - RTL8366S_PORT_VLAN_CTRL_SHIFT(port), - (index & RTL8366S_PORT_VLAN_CTRL_MASK) << - RTL8366S_PORT_VLAN_CTRL_SHIFT(port)); -} - -static int rtl8366s_enable_vlan(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, RTL8366S_SGCR_EN_VLAN, - (enable) ? RTL8366S_SGCR_EN_VLAN : 0); -} - -static int rtl8366s_enable_vlan4k(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366S_VLAN_TB_CTRL_REG, - 1, (enable) ? 1 : 0); -} - -static int rtl8366s_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) -{ - unsigned max = RTL8366S_NUM_VLANS; - - if (smi->vlan4k_enabled) - max = RTL8366S_NUM_VIDS - 1; - - if (vlan == 0 || vlan >= max) - return 0; - - return 1; -} - -static int rtl8366s_enable_port(struct rtl8366_smi *smi, int port, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8366S_PECR, (1 << port), - (enable) ? 0 : (1 << port)); -} - -static int rtl8366s_sw_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, 0, (1 << 2)); -} - -static int rtl8366s_sw_get_blinkrate(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366S_LED_BLINKRATE_REG, &data); - - val->value.i = (data & (RTL8366S_LED_BLINKRATE_MASK)); - - return 0; -} - -static int rtl8366s_sw_set_blinkrate(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->value.i >= 6) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8366S_LED_BLINKRATE_REG, - RTL8366S_LED_BLINKRATE_MASK, - val->value.i); -} - -static int rtl8366s_sw_get_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8366S_SGCR, &data); - - val->value.i = ((data & (RTL8366S_SGCR_MAX_LENGTH_MASK)) >> 4); - - return 0; -} - -static int rtl8366s_sw_set_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - char length_code; - - switch (val->value.i) { - case 0: - length_code = RTL8366S_SGCR_MAX_LENGTH_1522; - break; - case 1: - length_code = RTL8366S_SGCR_MAX_LENGTH_1536; - break; - case 2: - length_code = RTL8366S_SGCR_MAX_LENGTH_1552; - break; - case 3: - length_code = RTL8366S_SGCR_MAX_LENGTH_16000; - break; - default: - return -EINVAL; - } - - return rtl8366_smi_rmwr(smi, RTL8366S_SGCR, - RTL8366S_SGCR_MAX_LENGTH_MASK, - length_code); -} - -static int rtl8366s_sw_get_learning_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi,RTL8366S_SSCR0, &data); - val->value.i = !data; - - return 0; -} - - -static int rtl8366s_sw_set_learning_enable(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 portmask = 0; - int err = 0; - - if (!val->value.i) - portmask = RTL8366S_PORT_ALL; - - /* set learning for all ports */ - REG_WR(smi, RTL8366S_SSCR0, portmask); - - /* set auto ageing for all ports */ - REG_WR(smi, RTL8366S_SSCR1, portmask); - - return 0; -} - -static int rtl8366s_sw_get_green(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - int err; - - err = rtl8366_smi_read_reg(smi, RTL8366S_GREEN_ETHERNET_CTRL_REG, &data); - if (err) - return err; - - val->value.i = ((data & (RTL8366S_GREEN_ETHERNET_TX_BIT | RTL8366S_GREEN_ETHERNET_RX_BIT)) != 0) ? 1 : 0; - - return 0; -} - -static int rtl8366s_sw_set_green(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - return rtl8366s_set_green(smi, val->value.i); -} - -static int rtl8366s_sw_get_port_link(struct switch_dev *dev, - int port, - struct switch_port_link *link) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - u32 speed; - - if (port >= RTL8366S_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366S_PORT_LINK_STATUS_BASE + (port / 2), - &data); - - if (port % 2) - data = data >> 8; - - link->link = !!(data & RTL8366S_PORT_STATUS_LINK_MASK); - if (!link->link) - return 0; - - link->duplex = !!(data & RTL8366S_PORT_STATUS_DUPLEX_MASK); - link->rx_flow = !!(data & RTL8366S_PORT_STATUS_RXPAUSE_MASK); - link->tx_flow = !!(data & RTL8366S_PORT_STATUS_TXPAUSE_MASK); - link->aneg = !!(data & RTL8366S_PORT_STATUS_AN_MASK); - - speed = (data & RTL8366S_PORT_STATUS_SPEED_MASK); - switch (speed) { - case 0: - link->speed = SWITCH_PORT_SPEED_10; - break; - case 1: - link->speed = SWITCH_PORT_SPEED_100; - break; - case 2: - link->speed = SWITCH_PORT_SPEED_1000; - break; - default: - link->speed = SWITCH_PORT_SPEED_UNKNOWN; - break; - } - - return 0; -} - -static int rtl8366s_sw_set_port_led(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - u32 mask; - u32 reg; - - if (val->port_vlan >= RTL8366S_NUM_PORTS || - (1 << val->port_vlan) == RTL8366S_PORT_UNKNOWN) - return -EINVAL; - - if (val->port_vlan == RTL8366S_PORT_NUM_CPU) { - reg = RTL8366S_LED_BLINKRATE_REG; - mask = 0xF << 4; - data = val->value.i << 4; - } else { - reg = RTL8366S_LED_CTRL_REG; - mask = 0xF << (val->port_vlan * 4), - data = val->value.i << (val->port_vlan * 4); - } - - return rtl8366_smi_rmwr(smi, reg, mask, data); -} - -static int rtl8366s_sw_get_port_led(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - - if (val->port_vlan >= RTL8366S_NUM_LEDGROUPS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8366S_LED_CTRL_REG, &data); - val->value.i = (data >> (val->port_vlan * 4)) & 0x000F; - - return 0; -} - -static int rtl8366s_sw_get_green_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int err; - u32 phyData; - - if (val->port_vlan >= RTL8366S_NUM_PORTS) - return -EINVAL; - - err = rtl8366s_read_phy_reg(smi, val->port_vlan, 0, RTL8366S_PHY_POWER_SAVING_CTRL_REG, &phyData); - if (err) - return err; - - val->value.i = ((phyData & RTL8366S_PHY_POWER_SAVING_MASK) != 0) ? 1 : 0; - - return 0; -} - -static int rtl8366s_sw_set_green_port(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - return rtl8366s_set_green_port(smi, val->port_vlan, val->value.i); -} - -static int rtl8366s_sw_reset_port_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - if (val->port_vlan >= RTL8366S_NUM_PORTS) - return -EINVAL; - - - return rtl8366_smi_rmwr(smi, RTL8366S_MIB_CTRL_REG, - 0, (1 << (val->port_vlan + 3))); -} - -static int rtl8366s_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats) -{ - return (rtl8366_sw_get_port_stats(dev, port, stats, - RTL8366S_MIB_TXB_ID, RTL8366S_MIB_RXB_ID)); -} - -static struct switch_attr rtl8366s_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_learning", - .description = "Enable learning, enable aging", - .set = rtl8366s_sw_set_learning_enable, - .get = rtl8366s_sw_get_learning_enable, - .max = 1, - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan4k", - .description = "Enable VLAN 4K mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 2 - }, { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = rtl8366s_sw_reset_mibs, - }, { - .type = SWITCH_TYPE_INT, - .name = "blinkrate", - .description = "Get/Set LED blinking rate (0 = 43ms, 1 = 84ms," - " 2 = 120ms, 3 = 170ms, 4 = 340ms, 5 = 670ms)", - .set = rtl8366s_sw_set_blinkrate, - .get = rtl8366s_sw_get_blinkrate, - .max = 5 - }, { - .type = SWITCH_TYPE_INT, - .name = "max_length", - .description = "Get/Set the maximum length of valid packets" - " (0 = 1522, 1 = 1536, 2 = 1552, 3 = 16000 (9216?))", - .set = rtl8366s_sw_set_max_length, - .get = rtl8366s_sw_get_max_length, - .max = 3, - }, { - .type = SWITCH_TYPE_INT, - .name = "green_mode", - .description = "Get/Set the router green feature", - .set = rtl8366s_sw_set_green, - .get = rtl8366s_sw_get_green, - .max = 1, - }, -}; - -static struct switch_attr rtl8366s_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = rtl8366s_sw_reset_port_mibs, - }, { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get MIB counters for port", - .max = 33, - .set = NULL, - .get = rtl8366_sw_get_port_mib, - }, { - .type = SWITCH_TYPE_INT, - .name = "led", - .description = "Get/Set port group (0 - 3) led mode (0 - 15)", - .max = 15, - .set = rtl8366s_sw_set_port_led, - .get = rtl8366s_sw_get_port_led, - }, { - .type = SWITCH_TYPE_INT, - .name = "green_port", - .description = "Get/Set port green feature (0 - 1)", - .max = 1, - .set = rtl8366s_sw_set_green_port, - .get = rtl8366s_sw_get_green_port, - }, -}; - -static struct switch_attr rtl8366s_vlan[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "info", - .description = "Get vlan information", - .max = 1, - .set = NULL, - .get = rtl8366_sw_get_vlan_info, - }, { - .type = SWITCH_TYPE_INT, - .name = "fid", - .description = "Get/Set vlan FID", - .max = RTL8366S_FIDMAX, - .set = rtl8366_sw_set_vlan_fid, - .get = rtl8366_sw_get_vlan_fid, - }, -}; - -static const struct switch_dev_ops rtl8366_ops = { - .attr_global = { - .attr = rtl8366s_globals, - .n_attr = ARRAY_SIZE(rtl8366s_globals), - }, - .attr_port = { - .attr = rtl8366s_port, - .n_attr = ARRAY_SIZE(rtl8366s_port), - }, - .attr_vlan = { - .attr = rtl8366s_vlan, - .n_attr = ARRAY_SIZE(rtl8366s_vlan), - }, - - .get_vlan_ports = rtl8366_sw_get_vlan_ports, - .set_vlan_ports = rtl8366_sw_set_vlan_ports, - .get_port_pvid = rtl8366_sw_get_port_pvid, - .set_port_pvid = rtl8366_sw_set_port_pvid, - .reset_switch = rtl8366_sw_reset_switch, - .get_port_link = rtl8366s_sw_get_port_link, - .get_port_stats = rtl8366s_sw_get_port_stats, -}; - -static int rtl8366s_switch_init(struct rtl8366_smi *smi) -{ - struct switch_dev *dev = &smi->sw_dev; - int err; - - dev->name = "RTL8366S"; - dev->cpu_port = RTL8366S_PORT_NUM_CPU; - dev->ports = RTL8366S_NUM_PORTS; - dev->vlans = RTL8366S_NUM_VIDS; - dev->ops = &rtl8366_ops; - dev->alias = dev_name(smi->parent); - - err = register_switch(dev, NULL); - if (err) - dev_err(smi->parent, "switch registration failed\n"); - - return err; -} - -static void rtl8366s_switch_cleanup(struct rtl8366_smi *smi) -{ - unregister_switch(&smi->sw_dev); -} - -static int rtl8366s_mii_read(struct mii_bus *bus, int addr, int reg) -{ - struct rtl8366_smi *smi = bus->priv; - u32 val = 0; - int err; - - err = rtl8366s_read_phy_reg(smi, addr, 0, reg, &val); - if (err) - return 0xffff; - - return val; -} - -static int rtl8366s_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) -{ - struct rtl8366_smi *smi = bus->priv; - u32 t; - int err; - - err = rtl8366s_write_phy_reg(smi, addr, 0, reg, val); - /* flush write */ - (void) rtl8366s_read_phy_reg(smi, addr, 0, reg, &t); - - return err; -} - -static int rtl8366s_detect(struct rtl8366_smi *smi) -{ - u32 chip_id = 0; - u32 chip_ver = 0; - int ret; - - ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_ID_REG, &chip_id); - if (ret) { - dev_err(smi->parent, "unable to read chip id\n"); - return ret; - } - - switch (chip_id) { - case RTL8366S_CHIP_ID_8366: - break; - default: - dev_err(smi->parent, "unknown chip id (%04x)\n", chip_id); - return -ENODEV; - } - - ret = rtl8366_smi_read_reg(smi, RTL8366S_CHIP_VERSION_CTRL_REG, - &chip_ver); - if (ret) { - dev_err(smi->parent, "unable to read chip version\n"); - return ret; - } - - dev_info(smi->parent, "RTL%04x ver. %u chip found\n", - chip_id, chip_ver & RTL8366S_CHIP_VERSION_MASK); - - return 0; -} - -static struct rtl8366_smi_ops rtl8366s_smi_ops = { - .detect = rtl8366s_detect, - .reset_chip = rtl8366s_reset_chip, - .setup = rtl8366s_setup, - - .mii_read = rtl8366s_mii_read, - .mii_write = rtl8366s_mii_write, - - .get_vlan_mc = rtl8366s_get_vlan_mc, - .set_vlan_mc = rtl8366s_set_vlan_mc, - .get_vlan_4k = rtl8366s_get_vlan_4k, - .set_vlan_4k = rtl8366s_set_vlan_4k, - .get_mc_index = rtl8366s_get_mc_index, - .set_mc_index = rtl8366s_set_mc_index, - .get_mib_counter = rtl8366_get_mib_counter, - .is_vlan_valid = rtl8366s_is_vlan_valid, - .enable_vlan = rtl8366s_enable_vlan, - .enable_vlan4k = rtl8366s_enable_vlan4k, - .enable_port = rtl8366s_enable_port, -}; - -static int rtl8366s_probe(struct platform_device *pdev) -{ - static int rtl8366_smi_version_printed; - struct rtl8366_smi *smi; - int err; - - if (!rtl8366_smi_version_printed++) - printk(KERN_NOTICE RTL8366S_DRIVER_DESC - " version " RTL8366S_DRIVER_VER"\n"); - - smi = rtl8366_smi_probe(pdev); - if (!smi) - return -ENODEV; - - smi->clk_delay = 10; - smi->cmd_read = 0xa9; - smi->cmd_write = 0xa8; - smi->ops = &rtl8366s_smi_ops; - smi->cpu_port = RTL8366S_PORT_NUM_CPU; - smi->num_ports = RTL8366S_NUM_PORTS; - smi->num_vlan_mc = RTL8366S_NUM_VLANS; - smi->mib_counters = rtl8366s_mib_counters; - smi->num_mib_counters = ARRAY_SIZE(rtl8366s_mib_counters); - - err = rtl8366_smi_init(smi); - if (err) - goto err_free_smi; - - platform_set_drvdata(pdev, smi); - - err = rtl8366s_switch_init(smi); - if (err) - goto err_clear_drvdata; - - return 0; - - err_clear_drvdata: - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - err_free_smi: - kfree(smi); - return err; -} - -static int rtl8366s_remove(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) { - rtl8366s_switch_cleanup(smi); - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - kfree(smi); - } - - return 0; -} - -#ifdef CONFIG_OF -static const struct of_device_id rtl8366s_match[] = { - { .compatible = "realtek,rtl8366s" }, - {}, -}; -MODULE_DEVICE_TABLE(of, rtl8366s_match); -#endif - -static struct platform_driver rtl8366s_driver = { - .driver = { - .name = RTL8366S_DRIVER_NAME, - .owner = THIS_MODULE, -#ifdef CONFIG_OF - .of_match_table = of_match_ptr(rtl8366s_match), -#endif - }, - .probe = rtl8366s_probe, - .remove = rtl8366s_remove, -}; - -static int __init rtl8366s_module_init(void) -{ - return platform_driver_register(&rtl8366s_driver); -} -module_init(rtl8366s_module_init); - -static void __exit rtl8366s_module_exit(void) -{ - platform_driver_unregister(&rtl8366s_driver); -} -module_exit(rtl8366s_module_exit); - -MODULE_DESCRIPTION(RTL8366S_DRIVER_DESC); -MODULE_VERSION(RTL8366S_DRIVER_VER); -MODULE_AUTHOR("Gabor Juhos "); -MODULE_AUTHOR("Antti Seppälä "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8367.c b/target/linux/generic/files/drivers/net/phy/rtl8367.c deleted file mode 100644 index 9549961d76..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8367.c +++ /dev/null @@ -1,1846 +0,0 @@ -/* - * Platform driver for the Realtek RTL8367R/M ethernet switches - * - * Copyright (C) 2011 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rtl8366_smi.h" - -#define RTL8367_RESET_DELAY 1000 /* msecs*/ - -#define RTL8367_PHY_ADDR_MAX 8 -#define RTL8367_PHY_REG_MAX 31 - -#define RTL8367_VID_MASK 0xffff -#define RTL8367_FID_MASK 0xfff -#define RTL8367_UNTAG_MASK 0xffff -#define RTL8367_MEMBER_MASK 0xffff - -#define RTL8367_PORT_CFG_REG(_p) (0x000e + 0x20 * (_p)) -#define RTL8367_PORT_CFG_EGRESS_MODE_SHIFT 4 -#define RTL8367_PORT_CFG_EGRESS_MODE_MASK 0x3 -#define RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL 0 -#define RTL8367_PORT_CFG_EGRESS_MODE_KEEP 1 -#define RTL8367_PORT_CFG_EGRESS_MODE_PRI 2 -#define RTL8367_PORT_CFG_EGRESS_MODE_REAL 3 - -#define RTL8367_BYPASS_LINE_RATE_REG 0x03f7 - -#define RTL8367_TA_CTRL_REG 0x0500 -#define RTL8367_TA_CTRL_STATUS BIT(12) -#define RTL8367_TA_CTRL_METHOD BIT(5) -#define RTL8367_TA_CTRL_CMD_SHIFT 4 -#define RTL8367_TA_CTRL_CMD_READ 0 -#define RTL8367_TA_CTRL_CMD_WRITE 1 -#define RTL8367_TA_CTRL_TABLE_SHIFT 0 -#define RTL8367_TA_CTRL_TABLE_ACLRULE 1 -#define RTL8367_TA_CTRL_TABLE_ACLACT 2 -#define RTL8367_TA_CTRL_TABLE_CVLAN 3 -#define RTL8367_TA_CTRL_TABLE_L2 4 -#define RTL8367_TA_CTRL_CVLAN_READ \ - ((RTL8367_TA_CTRL_CMD_READ << RTL8367_TA_CTRL_CMD_SHIFT) | \ - RTL8367_TA_CTRL_TABLE_CVLAN) -#define RTL8367_TA_CTRL_CVLAN_WRITE \ - ((RTL8367_TA_CTRL_CMD_WRITE << RTL8367_TA_CTRL_CMD_SHIFT) | \ - RTL8367_TA_CTRL_TABLE_CVLAN) - -#define RTL8367_TA_ADDR_REG 0x0501 -#define RTL8367_TA_ADDR_MASK 0x3fff - -#define RTL8367_TA_DATA_REG(_x) (0x0503 + (_x)) -#define RTL8367_TA_VLAN_DATA_SIZE 4 -#define RTL8367_TA_VLAN_VID_MASK RTL8367_VID_MASK -#define RTL8367_TA_VLAN_MEMBER_SHIFT 0 -#define RTL8367_TA_VLAN_MEMBER_MASK RTL8367_MEMBER_MASK -#define RTL8367_TA_VLAN_FID_SHIFT 0 -#define RTL8367_TA_VLAN_FID_MASK RTL8367_FID_MASK -#define RTL8367_TA_VLAN_UNTAG1_SHIFT 14 -#define RTL8367_TA_VLAN_UNTAG1_MASK 0x3 -#define RTL8367_TA_VLAN_UNTAG2_SHIFT 0 -#define RTL8367_TA_VLAN_UNTAG2_MASK 0x3fff - -#define RTL8367_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) -#define RTL8367_VLAN_PVID_CTRL_MASK 0x1f -#define RTL8367_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) - -#define RTL8367_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) -#define RTL8367_VLAN_MC_DATA_SIZE 4 -#define RTL8367_VLAN_MC_MEMBER_SHIFT 0 -#define RTL8367_VLAN_MC_MEMBER_MASK RTL8367_MEMBER_MASK -#define RTL8367_VLAN_MC_FID_SHIFT 0 -#define RTL8367_VLAN_MC_FID_MASK RTL8367_FID_MASK -#define RTL8367_VLAN_MC_EVID_SHIFT 0 -#define RTL8367_VLAN_MC_EVID_MASK RTL8367_VID_MASK - -#define RTL8367_VLAN_CTRL_REG 0x07a8 -#define RTL8367_VLAN_CTRL_ENABLE BIT(0) - -#define RTL8367_VLAN_INGRESS_REG 0x07a9 - -#define RTL8367_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) - -#define RTL8367_MIB_COUNTER_REG(_x) (0x1000 + (_x)) - -#define RTL8367_MIB_ADDRESS_REG 0x1004 - -#define RTL8367_MIB_CTRL_REG(_x) (0x1005 + (_x)) -#define RTL8367_MIB_CTRL_GLOBAL_RESET_MASK BIT(11) -#define RTL8367_MIB_CTRL_QM_RESET_MASK BIT(10) -#define RTL8367_MIB_CTRL_PORT_RESET_MASK(_p) BIT(2 + (_p)) -#define RTL8367_MIB_CTRL_RESET_MASK BIT(1) -#define RTL8367_MIB_CTRL_BUSY_MASK BIT(0) - -#define RTL8367_MIB_COUNT 36 -#define RTL8367_MIB_COUNTER_PORT_OFFSET 0x0050 - -#define RTL8367_SWC0_REG 0x1200 -#define RTL8367_SWC0_MAX_LENGTH_SHIFT 13 -#define RTL8367_SWC0_MAX_LENGTH(_x) ((_x) << 13) -#define RTL8367_SWC0_MAX_LENGTH_MASK RTL8367_SWC0_MAX_LENGTH(0x3) -#define RTL8367_SWC0_MAX_LENGTH_1522 RTL8367_SWC0_MAX_LENGTH(0) -#define RTL8367_SWC0_MAX_LENGTH_1536 RTL8367_SWC0_MAX_LENGTH(1) -#define RTL8367_SWC0_MAX_LENGTH_1552 RTL8367_SWC0_MAX_LENGTH(2) -#define RTL8367_SWC0_MAX_LENGTH_16000 RTL8367_SWC0_MAX_LENGTH(3) - -#define RTL8367_CHIP_NUMBER_REG 0x1300 - -#define RTL8367_CHIP_VER_REG 0x1301 -#define RTL8367_CHIP_VER_RLVID_SHIFT 12 -#define RTL8367_CHIP_VER_RLVID_MASK 0xf -#define RTL8367_CHIP_VER_MCID_SHIFT 8 -#define RTL8367_CHIP_VER_MCID_MASK 0xf -#define RTL8367_CHIP_VER_BOID_SHIFT 4 -#define RTL8367_CHIP_VER_BOID_MASK 0xf - -#define RTL8367_CHIP_MODE_REG 0x1302 -#define RTL8367_CHIP_MODE_MASK 0x7 - -#define RTL8367_CHIP_DEBUG0_REG 0x1303 -#define RTL8367_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) - -#define RTL8367_CHIP_DEBUG1_REG 0x1304 - -#define RTL8367_DIS_REG 0x1305 -#define RTL8367_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) -#define RTL8367_DIS_RGMII_SHIFT(_x) (4 * (_x)) -#define RTL8367_DIS_RGMII_MASK 0x7 - -#define RTL8367_EXT_RGMXF_REG(_x) (0x1306 + (_x)) -#define RTL8367_EXT_RGMXF_DUMMY0_SHIFT 5 -#define RTL8367_EXT_RGMXF_DUMMY0_MASK 0x7ff -#define RTL8367_EXT_RGMXF_TXDELAY_SHIFT 3 -#define RTL8367_EXT_RGMXF_TXDELAY_MASK 1 -#define RTL8367_EXT_RGMXF_RXDELAY_MASK 0x7 - -#define RTL8367_DI_FORCE_REG(_x) (0x1310 + (_x)) -#define RTL8367_DI_FORCE_MODE BIT(12) -#define RTL8367_DI_FORCE_NWAY BIT(7) -#define RTL8367_DI_FORCE_TXPAUSE BIT(6) -#define RTL8367_DI_FORCE_RXPAUSE BIT(5) -#define RTL8367_DI_FORCE_LINK BIT(4) -#define RTL8367_DI_FORCE_DUPLEX BIT(2) -#define RTL8367_DI_FORCE_SPEED_MASK 3 -#define RTL8367_DI_FORCE_SPEED_10 0 -#define RTL8367_DI_FORCE_SPEED_100 1 -#define RTL8367_DI_FORCE_SPEED_1000 2 - -#define RTL8367_MAC_FORCE_REG(_x) (0x1312 + (_x)) - -#define RTL8367_CHIP_RESET_REG 0x1322 -#define RTL8367_CHIP_RESET_SW BIT(1) -#define RTL8367_CHIP_RESET_HW BIT(0) - -#define RTL8367_PORT_STATUS_REG(_p) (0x1352 + (_p)) -#define RTL8367_PORT_STATUS_NWAY BIT(7) -#define RTL8367_PORT_STATUS_TXPAUSE BIT(6) -#define RTL8367_PORT_STATUS_RXPAUSE BIT(5) -#define RTL8367_PORT_STATUS_LINK BIT(4) -#define RTL8367_PORT_STATUS_DUPLEX BIT(2) -#define RTL8367_PORT_STATUS_SPEED_MASK 0x0003 -#define RTL8367_PORT_STATUS_SPEED_10 0 -#define RTL8367_PORT_STATUS_SPEED_100 1 -#define RTL8367_PORT_STATUS_SPEED_1000 2 - -#define RTL8367_RTL_NO_REG 0x13c0 -#define RTL8367_RTL_NO_8367R 0x3670 -#define RTL8367_RTL_NO_8367M 0x3671 - -#define RTL8367_RTL_VER_REG 0x13c1 -#define RTL8367_RTL_VER_MASK 0xf - -#define RTL8367_RTL_MAGIC_ID_REG 0x13c2 -#define RTL8367_RTL_MAGIC_ID_VAL 0x0249 - -#define RTL8367_LED_SYS_CONFIG_REG 0x1b00 -#define RTL8367_LED_MODE_REG 0x1b02 -#define RTL8367_LED_MODE_RATE_M 0x7 -#define RTL8367_LED_MODE_RATE_S 1 - -#define RTL8367_LED_CONFIG_REG 0x1b03 -#define RTL8367_LED_CONFIG_DATA_S 12 -#define RTL8367_LED_CONFIG_DATA_M 0x3 -#define RTL8367_LED_CONFIG_SEL BIT(14) -#define RTL8367_LED_CONFIG_LED_CFG_M 0xf - -#define RTL8367_PARA_LED_IO_EN1_REG 0x1b24 -#define RTL8367_PARA_LED_IO_EN2_REG 0x1b25 -#define RTL8367_PARA_LED_IO_EN_PMASK 0xff - -#define RTL8367_IA_CTRL_REG 0x1f00 -#define RTL8367_IA_CTRL_RW(_x) ((_x) << 1) -#define RTL8367_IA_CTRL_RW_READ RTL8367_IA_CTRL_RW(0) -#define RTL8367_IA_CTRL_RW_WRITE RTL8367_IA_CTRL_RW(1) -#define RTL8367_IA_CTRL_CMD_MASK BIT(0) - -#define RTL8367_IA_STATUS_REG 0x1f01 -#define RTL8367_IA_STATUS_PHY_BUSY BIT(2) -#define RTL8367_IA_STATUS_SDS_BUSY BIT(1) -#define RTL8367_IA_STATUS_MDX_BUSY BIT(0) - -#define RTL8367_IA_ADDRESS_REG 0x1f02 - -#define RTL8367_IA_WRITE_DATA_REG 0x1f03 -#define RTL8367_IA_READ_DATA_REG 0x1f04 - -#define RTL8367_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) - -#define RTL8367_CPU_PORT_NUM 9 -#define RTL8367_NUM_PORTS 10 -#define RTL8367_NUM_VLANS 32 -#define RTL8367_NUM_LEDGROUPS 4 -#define RTL8367_NUM_VIDS 4096 -#define RTL8367_PRIORITYMAX 7 -#define RTL8367_FIDMAX 7 - -#define RTL8367_PORT_0 BIT(0) -#define RTL8367_PORT_1 BIT(1) -#define RTL8367_PORT_2 BIT(2) -#define RTL8367_PORT_3 BIT(3) -#define RTL8367_PORT_4 BIT(4) -#define RTL8367_PORT_5 BIT(5) -#define RTL8367_PORT_6 BIT(6) -#define RTL8367_PORT_7 BIT(7) -#define RTL8367_PORT_E1 BIT(8) /* external port 1 */ -#define RTL8367_PORT_E0 BIT(9) /* external port 0 */ - -#define RTL8367_PORTS_ALL \ - (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ - RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ - RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1 | \ - RTL8367_PORT_E0) - -#define RTL8367_PORTS_ALL_BUT_CPU \ - (RTL8367_PORT_0 | RTL8367_PORT_1 | RTL8367_PORT_2 | \ - RTL8367_PORT_3 | RTL8367_PORT_4 | RTL8367_PORT_5 | \ - RTL8367_PORT_6 | RTL8367_PORT_7 | RTL8367_PORT_E1) - -struct rtl8367_initval { - u16 reg; - u16 val; -}; - -#define RTL8367_MIB_RXB_ID 0 /* IfInOctets */ -#define RTL8367_MIB_TXB_ID 20 /* IfOutOctets */ - -static struct rtl8366_mib_counter rtl8367_mib_counters[] = { - { 0, 0, 4, "IfInOctets" }, - { 0, 4, 2, "Dot3StatsFCSErrors" }, - { 0, 6, 2, "Dot3StatsSymbolErrors" }, - { 0, 8, 2, "Dot3InPauseFrames" }, - { 0, 10, 2, "Dot3ControlInUnknownOpcodes" }, - { 0, 12, 2, "EtherStatsFragments" }, - { 0, 14, 2, "EtherStatsJabbers" }, - { 0, 16, 2, "IfInUcastPkts" }, - { 0, 18, 2, "EtherStatsDropEvents" }, - { 0, 20, 4, "EtherStatsOctets" }, - - { 0, 24, 2, "EtherStatsUnderSizePkts" }, - { 0, 26, 2, "EtherOversizeStats" }, - { 0, 28, 2, "EtherStatsPkts64Octets" }, - { 0, 30, 2, "EtherStatsPkts65to127Octets" }, - { 0, 32, 2, "EtherStatsPkts128to255Octets" }, - { 0, 34, 2, "EtherStatsPkts256to511Octets" }, - { 0, 36, 2, "EtherStatsPkts512to1023Octets" }, - { 0, 38, 2, "EtherStatsPkts1024to1518Octets" }, - { 0, 40, 2, "EtherStatsMulticastPkts" }, - { 0, 42, 2, "EtherStatsBroadcastPkts" }, - - { 0, 44, 4, "IfOutOctets" }, - - { 0, 48, 2, "Dot3StatsSingleCollisionFrames" }, - { 0, 50, 2, "Dot3StatMultipleCollisionFrames" }, - { 0, 52, 2, "Dot3sDeferredTransmissions" }, - { 0, 54, 2, "Dot3StatsLateCollisions" }, - { 0, 56, 2, "EtherStatsCollisions" }, - { 0, 58, 2, "Dot3StatsExcessiveCollisions" }, - { 0, 60, 2, "Dot3OutPauseFrames" }, - { 0, 62, 2, "Dot1dBasePortDelayExceededDiscards" }, - { 0, 64, 2, "Dot1dTpPortInDiscards" }, - { 0, 66, 2, "IfOutUcastPkts" }, - { 0, 68, 2, "IfOutMulticastPkts" }, - { 0, 70, 2, "IfOutBroadcastPkts" }, - { 0, 72, 2, "OutOampduPkts" }, - { 0, 74, 2, "InOampduPkts" }, - { 0, 76, 2, "PktgenPkts" }, -}; - -#define REG_RD(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_read_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_WR(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_write_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_RMW(_smi, _reg, _mask, _val) \ - do { \ - err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ - if (err) \ - return err; \ - } while (0) - -static const struct rtl8367_initval rtl8367_initvals_0_0[] = { - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, - {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, - {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, - {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, - {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, - {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, - {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, - {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, - {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, - {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, - {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, - {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, - {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, - {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, - {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, - {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, - {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, - {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, - {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, - {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, - {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, - {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, - {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1006}, {0x121e, 0x03e8}, - {0x121f, 0x02b3}, {0x1220, 0x028f}, {0x1221, 0x029b}, {0x1222, 0x0277}, - {0x1223, 0x02b3}, {0x1224, 0x028f}, {0x1225, 0x029b}, {0x1226, 0x0277}, - {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, {0x1230, 0x00b4}, - {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, - {0x0219, 0x0032}, {0x0200, 0x03e8}, {0x0201, 0x03e8}, {0x0202, 0x03e8}, - {0x0203, 0x03e8}, {0x0204, 0x03e8}, {0x0205, 0x03e8}, {0x0206, 0x03e8}, - {0x0207, 0x03e8}, {0x0218, 0x0032}, {0x0208, 0x029b}, {0x0209, 0x029b}, - {0x020a, 0x029b}, {0x020b, 0x029b}, {0x020c, 0x029b}, {0x020d, 0x029b}, - {0x020e, 0x029b}, {0x020f, 0x029b}, {0x0210, 0x029b}, {0x0211, 0x029b}, - {0x0212, 0x029b}, {0x0213, 0x029b}, {0x0214, 0x029b}, {0x0215, 0x029b}, - {0x0216, 0x029b}, {0x0217, 0x029b}, {0x0900, 0x0000}, {0x0901, 0x0000}, - {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, - {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, - {0x0802, 0x0100}, {0x1700, 0x014C}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, - {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, - {0x133f, 0x0010}, {0x20A0, 0x1940}, {0x20C0, 0x1940}, {0x20E0, 0x1940}, -}; - -static const struct rtl8367_initval rtl8367_initvals_0_1[] = { - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0000}, {0x2215, 0x1006}, - {0x221f, 0x0005}, {0x2200, 0x00c6}, {0x221f, 0x0007}, {0x221e, 0x0048}, - {0x2215, 0x6412}, {0x2216, 0x6412}, {0x2217, 0x6412}, {0x2218, 0x6412}, - {0x2219, 0x6412}, {0x221A, 0x6412}, {0x221f, 0x0001}, {0x220c, 0xdbf0}, - {0x2209, 0x2576}, {0x2207, 0x287E}, {0x220A, 0x68E5}, {0x221D, 0x3DA4}, - {0x221C, 0xE7F7}, {0x2214, 0x7F52}, {0x2218, 0x7FCE}, {0x2208, 0x04B7}, - {0x2206, 0x4072}, {0x2210, 0xF05E}, {0x221B, 0xB414}, {0x221F, 0x0003}, - {0x221A, 0x06A6}, {0x2210, 0xF05E}, {0x2213, 0x06EB}, {0x2212, 0xF4D2}, - {0x220E, 0xE120}, {0x2200, 0x7C00}, {0x2202, 0x5FD0}, {0x220D, 0x0207}, - {0x221f, 0x0002}, {0x2205, 0x0978}, {0x2202, 0x8C01}, {0x2207, 0x3620}, - {0x221C, 0x0001}, {0x2203, 0x0420}, {0x2204, 0x80C8}, {0x133e, 0x0ede}, - {0x221f, 0x0002}, {0x220c, 0x0073}, {0x220d, 0xEB65}, {0x220e, 0x51d1}, - {0x220f, 0x5dcb}, {0x2210, 0x3044}, {0x2211, 0x1800}, {0x2212, 0x7E00}, - {0x2213, 0x0000}, {0x133f, 0x0010}, {0x133e, 0x0ffe}, {0x207f, 0x0002}, - {0x2074, 0x3D22}, {0x2075, 0x2000}, {0x2076, 0x6040}, {0x2077, 0x0000}, - {0x2078, 0x0f0a}, {0x2079, 0x50AB}, {0x207a, 0x0000}, {0x207b, 0x0f0f}, - {0x205f, 0x0002}, {0x2054, 0xFF00}, {0x2055, 0x000A}, {0x2056, 0x000A}, - {0x2057, 0x0005}, {0x2058, 0x0005}, {0x2059, 0x0000}, {0x205A, 0x0005}, - {0x205B, 0x0005}, {0x205C, 0x0005}, {0x209f, 0x0002}, {0x2094, 0x00AA}, - {0x2095, 0x00AA}, {0x2096, 0x00AA}, {0x2097, 0x00AA}, {0x2098, 0x0055}, - {0x2099, 0x00AA}, {0x209A, 0x00AA}, {0x209B, 0x00AA}, {0x1363, 0x8354}, - {0x1270, 0x3333}, {0x1271, 0x3333}, {0x1272, 0x3333}, {0x1330, 0x00DB}, - {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x1b06}, {0x121e, 0x07f0}, - {0x121f, 0x0438}, {0x1220, 0x040f}, {0x1221, 0x040f}, {0x1222, 0x03eb}, - {0x1223, 0x0438}, {0x1224, 0x040f}, {0x1225, 0x040f}, {0x1226, 0x03eb}, - {0x1227, 0x0144}, {0x1228, 0x0138}, {0x122f, 0x0144}, {0x1230, 0x0138}, - {0x1229, 0x0020}, {0x122a, 0x000c}, {0x1231, 0x0030}, {0x1232, 0x0024}, - {0x0219, 0x0032}, {0x0200, 0x07d0}, {0x0201, 0x07d0}, {0x0202, 0x07d0}, - {0x0203, 0x07d0}, {0x0204, 0x07d0}, {0x0205, 0x07d0}, {0x0206, 0x07d0}, - {0x0207, 0x07d0}, {0x0218, 0x0032}, {0x0208, 0x0190}, {0x0209, 0x0190}, - {0x020a, 0x0190}, {0x020b, 0x0190}, {0x020c, 0x0190}, {0x020d, 0x0190}, - {0x020e, 0x0190}, {0x020f, 0x0190}, {0x0210, 0x0190}, {0x0211, 0x0190}, - {0x0212, 0x0190}, {0x0213, 0x0190}, {0x0214, 0x0190}, {0x0215, 0x0190}, - {0x0216, 0x0190}, {0x0217, 0x0190}, {0x0900, 0x0000}, {0x0901, 0x0000}, - {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, - {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, - {0x0802, 0x0100}, {0x1700, 0x0125}, {0x0301, 0x00FF}, {0x12AA, 0x0096}, - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0005}, {0x2200, 0x00C4}, - {0x221f, 0x0000}, {0x2210, 0x05EF}, {0x2204, 0x05E1}, {0x2200, 0x1340}, - {0x133f, 0x0010}, -}; - -static const struct rtl8367_initval rtl8367_initvals_1_0[] = { - {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, - {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, - {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, - {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, - {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, - {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, - {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, - {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, - {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, - {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, - {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, - {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, - {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, - {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, - {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, - {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, - {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, - {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, - {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, - {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, - {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, - {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, - {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, - {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, - {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, - {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, - {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, - {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, - {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, - {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, - {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, - {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, - {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, - {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, - {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, - {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, - {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, - {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, - {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, - {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, - {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, - {0x121D, 0x7D16}, {0x121E, 0x03E8}, {0x121F, 0x024E}, {0x1220, 0x0230}, - {0x1221, 0x0244}, {0x1222, 0x0226}, {0x1223, 0x024E}, {0x1224, 0x0230}, - {0x1225, 0x0244}, {0x1226, 0x0226}, {0x1227, 0x00C0}, {0x1228, 0x00B4}, - {0x122F, 0x00C0}, {0x1230, 0x00B4}, {0x0208, 0x03E8}, {0x0209, 0x03E8}, - {0x020A, 0x03E8}, {0x020B, 0x03E8}, {0x020C, 0x03E8}, {0x020D, 0x03E8}, - {0x020E, 0x03E8}, {0x020F, 0x03E8}, {0x0210, 0x03E8}, {0x0211, 0x03E8}, - {0x0212, 0x03E8}, {0x0213, 0x03E8}, {0x0214, 0x03E8}, {0x0215, 0x03E8}, - {0x0216, 0x03E8}, {0x0217, 0x03E8}, {0x0900, 0x0000}, {0x0901, 0x0000}, - {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087B, 0x0000}, - {0x087C, 0xFF00}, {0x087D, 0x0000}, {0x087E, 0x0000}, {0x0801, 0x0100}, - {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, - {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, - {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, {0x2200, 0x1340}, - {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x20A0, 0x1940}, - {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, -}; - -static const struct rtl8367_initval rtl8367_initvals_1_1[] = { - {0x1B24, 0x0000}, {0x1B25, 0x0000}, {0x1B26, 0x0000}, {0x1B27, 0x0000}, - {0x207F, 0x0002}, {0x2079, 0x0200}, {0x207F, 0x0000}, {0x133F, 0x0030}, - {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2201, 0x0700}, {0x2205, 0x8B82}, - {0x2206, 0x05CB}, {0x221F, 0x0002}, {0x2204, 0x80C2}, {0x2205, 0x0938}, - {0x221F, 0x0003}, {0x2212, 0xC4D2}, {0x220D, 0x0207}, {0x221F, 0x0001}, - {0x2207, 0x267E}, {0x221C, 0xE5F7}, {0x221B, 0x0424}, {0x221F, 0x0007}, - {0x221E, 0x0040}, {0x2218, 0x0000}, {0x221F, 0x0007}, {0x221E, 0x002C}, - {0x2218, 0x008B}, {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, - {0x2205, 0x8000}, {0x2206, 0xF8E0}, {0x2206, 0xE000}, {0x2206, 0xE1E0}, - {0x2206, 0x01AC}, {0x2206, 0x2408}, {0x2206, 0xE08B}, {0x2206, 0x84F7}, - {0x2206, 0x20E4}, {0x2206, 0x8B84}, {0x2206, 0xFC05}, {0x2206, 0xF8FA}, - {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AC}, {0x2206, 0x201A}, - {0x2206, 0xBF80}, {0x2206, 0x59D0}, {0x2206, 0x2402}, {0x2206, 0x803D}, - {0x2206, 0xE0E0}, {0x2206, 0xE4E1}, {0x2206, 0xE0E5}, {0x2206, 0x5806}, - {0x2206, 0x68C0}, {0x2206, 0xD1D2}, {0x2206, 0xE4E0}, {0x2206, 0xE4E5}, - {0x2206, 0xE0E5}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x05FB}, - {0x2206, 0x0BFB}, {0x2206, 0x58FF}, {0x2206, 0x9E11}, {0x2206, 0x06F0}, - {0x2206, 0x0C81}, {0x2206, 0x8AE0}, {0x2206, 0x0019}, {0x2206, 0x1B89}, - {0x2206, 0xCFEB}, {0x2206, 0x19EB}, {0x2206, 0x19B0}, {0x2206, 0xEFFF}, - {0x2206, 0x0BFF}, {0x2206, 0x0425}, {0x2206, 0x0807}, {0x2206, 0x2640}, - {0x2206, 0x7227}, {0x2206, 0x267E}, {0x2206, 0x2804}, {0x2206, 0xB729}, - {0x2206, 0x2576}, {0x2206, 0x2A68}, {0x2206, 0xE52B}, {0x2206, 0xAD00}, - {0x2206, 0x2CDB}, {0x2206, 0xF02D}, {0x2206, 0x67BB}, {0x2206, 0x2E7B}, - {0x2206, 0x0F2F}, {0x2206, 0x7365}, {0x2206, 0x31AC}, {0x2206, 0xCC32}, - {0x2206, 0x2300}, {0x2206, 0x332D}, {0x2206, 0x1734}, {0x2206, 0x7F52}, - {0x2206, 0x3510}, {0x2206, 0x0036}, {0x2206, 0x0600}, {0x2206, 0x370C}, - {0x2206, 0xC038}, {0x2206, 0x7FCE}, {0x2206, 0x3CE5}, {0x2206, 0xF73D}, - {0x2206, 0x3DA4}, {0x2206, 0x6530}, {0x2206, 0x3E67}, {0x2206, 0x0053}, - {0x2206, 0x69D2}, {0x2206, 0x0F6A}, {0x2206, 0x012C}, {0x2206, 0x6C2B}, - {0x2206, 0x136E}, {0x2206, 0xE100}, {0x2206, 0x6F12}, {0x2206, 0xF771}, - {0x2206, 0x006B}, {0x2206, 0x7306}, {0x2206, 0xEB74}, {0x2206, 0x94C7}, - {0x2206, 0x7698}, {0x2206, 0x0A77}, {0x2206, 0x5000}, {0x2206, 0x788A}, - {0x2206, 0x1579}, {0x2206, 0x7F6F}, {0x2206, 0x7A06}, {0x2206, 0xA600}, - {0x2205, 0x8B90}, {0x2206, 0x8000}, {0x2205, 0x8B92}, {0x2206, 0x8000}, - {0x2205, 0x8B94}, {0x2206, 0x8014}, {0x2208, 0xFFFA}, {0x2202, 0x3C65}, - {0x2205, 0xFFF6}, {0x2206, 0x00F7}, {0x221F, 0x0000}, {0x221F, 0x0007}, - {0x221E, 0x0042}, {0x2218, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, - {0x221E, 0x0020}, {0x2215, 0x0000}, {0x221E, 0x0023}, {0x2216, 0x8000}, - {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, {0x1362, 0x0115}, - {0x1363, 0x0002}, {0x1363, 0x0000}, {0x1306, 0x000C}, {0x1307, 0x000C}, - {0x1303, 0x0067}, {0x1304, 0x4444}, {0x1203, 0xFF00}, {0x1200, 0x7FC4}, - {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, - {0x0865, 0x3210}, {0x087B, 0x0000}, {0x087C, 0xFF00}, {0x087D, 0x0000}, - {0x087E, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, - {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, - {0x0A25, 0x2040}, {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, - {0x0A29, 0x2040}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x221F, 0x0000}, - {0x2200, 0x1340}, {0x221F, 0x0000}, {0x133F, 0x0010}, {0x133E, 0x0FFE}, - {0x1B03, 0x0876}, -}; - -static const struct rtl8367_initval rtl8367_initvals_2_0[] = { - {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, - {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, - {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, - {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, - {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, - {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, - {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, - {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, - {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, - {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, - {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, - {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, - {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, - {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, - {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, - {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, - {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, - {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, - {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, - {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, - {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, - {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, - {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, - {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, - {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, - {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, - {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, - {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, - {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, - {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, - {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, - {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, - {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, - {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, - {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, - {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, - {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x121d, 0x7D16}, - {0x121e, 0x03e8}, {0x121f, 0x024e}, {0x1220, 0x0230}, {0x1221, 0x0244}, - {0x1222, 0x0226}, {0x1223, 0x024e}, {0x1224, 0x0230}, {0x1225, 0x0244}, - {0x1226, 0x0226}, {0x1227, 0x00c0}, {0x1228, 0x00b4}, {0x122f, 0x00c0}, - {0x1230, 0x00b4}, {0x0208, 0x03e8}, {0x0209, 0x03e8}, {0x020a, 0x03e8}, - {0x020b, 0x03e8}, {0x020c, 0x03e8}, {0x020d, 0x03e8}, {0x020e, 0x03e8}, - {0x020f, 0x03e8}, {0x0210, 0x03e8}, {0x0211, 0x03e8}, {0x0212, 0x03e8}, - {0x0213, 0x03e8}, {0x0214, 0x03e8}, {0x0215, 0x03e8}, {0x0216, 0x03e8}, - {0x0217, 0x03e8}, {0x0900, 0x0000}, {0x0901, 0x0000}, {0x0902, 0x0000}, - {0x0903, 0x0000}, {0x0865, 0x3210}, {0x087b, 0x0000}, {0x087c, 0xff00}, - {0x087d, 0x0000}, {0x087e, 0x0000}, {0x0801, 0x0100}, {0x0802, 0x0100}, - {0x0A20, 0x2040}, {0x0A21, 0x2040}, {0x0A22, 0x2040}, {0x0A23, 0x2040}, - {0x0A24, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, {0x20A0, 0x1940}, - {0x20C0, 0x1940}, {0x20E0, 0x1940}, {0x130c, 0x0050}, -}; - -static const struct rtl8367_initval rtl8367_initvals_2_1[] = { - {0x1b24, 0x0000}, {0x1b25, 0x0000}, {0x1b26, 0x0000}, {0x1b27, 0x0000}, - {0x133f, 0x0030}, {0x133e, 0x000e}, {0x221f, 0x0007}, {0x221e, 0x0048}, - {0x2219, 0x4012}, {0x221f, 0x0003}, {0x2201, 0x3554}, {0x2202, 0x63e8}, - {0x2203, 0x99c2}, {0x2204, 0x0113}, {0x2205, 0x303e}, {0x220d, 0x0207}, - {0x220e, 0xe100}, {0x221f, 0x0007}, {0x221e, 0x0040}, {0x2218, 0x0000}, - {0x221f, 0x0007}, {0x221e, 0x002c}, {0x2218, 0x008b}, {0x221f, 0x0005}, - {0x2205, 0xfff6}, {0x2206, 0x0080}, {0x221f, 0x0005}, {0x2205, 0x8000}, - {0x2206, 0x0280}, {0x2206, 0x2bf7}, {0x2206, 0x00e0}, {0x2206, 0xfff7}, - {0x2206, 0xa080}, {0x2206, 0x02ae}, {0x2206, 0xf602}, {0x2206, 0x804e}, - {0x2206, 0x0201}, {0x2206, 0x5002}, {0x2206, 0x0163}, {0x2206, 0x0201}, - {0x2206, 0x79e0}, {0x2206, 0x8b8c}, {0x2206, 0xe18b}, {0x2206, 0x8d1e}, - {0x2206, 0x01e1}, {0x2206, 0x8b8e}, {0x2206, 0x1e01}, {0x2206, 0xa000}, - {0x2206, 0xe4ae}, {0x2206, 0xd8bf}, {0x2206, 0x8b88}, {0x2206, 0xec00}, - {0x2206, 0x19a9}, {0x2206, 0x8b90}, {0x2206, 0xf9ee}, {0x2206, 0xfff6}, - {0x2206, 0x00ee}, {0x2206, 0xfff7}, {0x2206, 0xfce0}, {0x2206, 0xe140}, - {0x2206, 0xe1e1}, {0x2206, 0x41f7}, {0x2206, 0x2ff6}, {0x2206, 0x28e4}, - {0x2206, 0xe140}, {0x2206, 0xe5e1}, {0x2206, 0x4104}, {0x2206, 0xf8fa}, - {0x2206, 0xef69}, {0x2206, 0xe08b}, {0x2206, 0x86ac}, {0x2206, 0x201a}, - {0x2206, 0xbf80}, {0x2206, 0x77d0}, {0x2206, 0x6c02}, {0x2206, 0x2978}, - {0x2206, 0xe0e0}, {0x2206, 0xe4e1}, {0x2206, 0xe0e5}, {0x2206, 0x5806}, - {0x2206, 0x68c0}, {0x2206, 0xd1d2}, {0x2206, 0xe4e0}, {0x2206, 0xe4e5}, - {0x2206, 0xe0e5}, {0x2206, 0xef96}, {0x2206, 0xfefc}, {0x2206, 0x0425}, - {0x2206, 0x0807}, {0x2206, 0x2640}, {0x2206, 0x7227}, {0x2206, 0x267e}, - {0x2206, 0x2804}, {0x2206, 0xb729}, {0x2206, 0x2576}, {0x2206, 0x2a68}, - {0x2206, 0xe52b}, {0x2206, 0xad00}, {0x2206, 0x2cdb}, {0x2206, 0xf02d}, - {0x2206, 0x67bb}, {0x2206, 0x2e7b}, {0x2206, 0x0f2f}, {0x2206, 0x7365}, - {0x2206, 0x31ac}, {0x2206, 0xcc32}, {0x2206, 0x2300}, {0x2206, 0x332d}, - {0x2206, 0x1734}, {0x2206, 0x7f52}, {0x2206, 0x3510}, {0x2206, 0x0036}, - {0x2206, 0x0600}, {0x2206, 0x370c}, {0x2206, 0xc038}, {0x2206, 0x7fce}, - {0x2206, 0x3ce5}, {0x2206, 0xf73d}, {0x2206, 0x3da4}, {0x2206, 0x6530}, - {0x2206, 0x3e67}, {0x2206, 0x0053}, {0x2206, 0x69d2}, {0x2206, 0x0f6a}, - {0x2206, 0x012c}, {0x2206, 0x6c2b}, {0x2206, 0x136e}, {0x2206, 0xe100}, - {0x2206, 0x6f12}, {0x2206, 0xf771}, {0x2206, 0x006b}, {0x2206, 0x7306}, - {0x2206, 0xeb74}, {0x2206, 0x94c7}, {0x2206, 0x7698}, {0x2206, 0x0a77}, - {0x2206, 0x5000}, {0x2206, 0x788a}, {0x2206, 0x1579}, {0x2206, 0x7f6f}, - {0x2206, 0x7a06}, {0x2206, 0xa600}, {0x2201, 0x0701}, {0x2200, 0x0405}, - {0x221f, 0x0000}, {0x2200, 0x1340}, {0x221f, 0x0000}, {0x133f, 0x0010}, - {0x133e, 0x0ffe}, {0x1203, 0xff00}, {0x1200, 0x7fc4}, {0x0900, 0x0000}, - {0x0901, 0x0000}, {0x0902, 0x0000}, {0x0903, 0x0000}, {0x0865, 0x3210}, - {0x087b, 0x0000}, {0x087c, 0xff00}, {0x087d, 0x0000}, {0x087e, 0x0000}, - {0x0801, 0x0100}, {0x0802, 0x0100}, {0x0A20, 0x2040}, {0x0A21, 0x2040}, - {0x0A22, 0x2040}, {0x0A23, 0x2040}, {0x0A24, 0x2040}, {0x0A25, 0x2040}, - {0x0A26, 0x2040}, {0x0A27, 0x2040}, {0x0A28, 0x2040}, {0x0A29, 0x2040}, - {0x130c, 0x0050}, -}; - -static int rtl8367_write_initvals(struct rtl8366_smi *smi, - const struct rtl8367_initval *initvals, - int count) -{ - int err; - int i; - - for (i = 0; i < count; i++) - REG_WR(smi, initvals[i].reg, initvals[i].val); - - return 0; -} - -static int rtl8367_read_phy_reg(struct rtl8366_smi *smi, - u32 phy_addr, u32 phy_reg, u32 *val) -{ - int timeout; - u32 data; - int err; - - if (phy_addr > RTL8367_PHY_ADDR_MAX) - return -EINVAL; - - if (phy_reg > RTL8367_PHY_REG_MAX) - return -EINVAL; - - REG_RD(smi, RTL8367_IA_STATUS_REG, &data); - if (data & RTL8367_IA_STATUS_PHY_BUSY) - return -ETIMEDOUT; - - /* prepare address */ - REG_WR(smi, RTL8367_IA_ADDRESS_REG, - RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); - - /* send read command */ - REG_WR(smi, RTL8367_IA_CTRL_REG, - RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_READ); - - timeout = 5; - do { - REG_RD(smi, RTL8367_IA_STATUS_REG, &data); - if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) - break; - - if (timeout--) { - dev_err(smi->parent, "phy read timed out\n"); - return -ETIMEDOUT; - } - - udelay(1); - } while (1); - - /* read data */ - REG_RD(smi, RTL8367_IA_READ_DATA_REG, val); - - dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", - phy_addr, phy_reg, *val); - return 0; -} - -static int rtl8367_write_phy_reg(struct rtl8366_smi *smi, - u32 phy_addr, u32 phy_reg, u32 val) -{ - int timeout; - u32 data; - int err; - - dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", - phy_addr, phy_reg, val); - - if (phy_addr > RTL8367_PHY_ADDR_MAX) - return -EINVAL; - - if (phy_reg > RTL8367_PHY_REG_MAX) - return -EINVAL; - - REG_RD(smi, RTL8367_IA_STATUS_REG, &data); - if (data & RTL8367_IA_STATUS_PHY_BUSY) - return -ETIMEDOUT; - - /* preapre data */ - REG_WR(smi, RTL8367_IA_WRITE_DATA_REG, val); - - /* prepare address */ - REG_WR(smi, RTL8367_IA_ADDRESS_REG, - RTL8367_INTERNAL_PHY_REG(phy_addr, phy_reg)); - - /* send write command */ - REG_WR(smi, RTL8367_IA_CTRL_REG, - RTL8367_IA_CTRL_CMD_MASK | RTL8367_IA_CTRL_RW_WRITE); - - timeout = 5; - do { - REG_RD(smi, RTL8367_IA_STATUS_REG, &data); - if ((data & RTL8367_IA_STATUS_PHY_BUSY) == 0) - break; - - if (timeout--) { - dev_err(smi->parent, "phy write timed out\n"); - return -ETIMEDOUT; - } - - udelay(1); - } while (1); - - return 0; -} - -static int rtl8367_init_regs0(struct rtl8366_smi *smi, unsigned mode) -{ - const struct rtl8367_initval *initvals; - int count; - int err; - - switch (mode) { - case 0: - initvals = rtl8367_initvals_0_0; - count = ARRAY_SIZE(rtl8367_initvals_0_0); - break; - - case 1: - case 2: - initvals = rtl8367_initvals_0_1; - count = ARRAY_SIZE(rtl8367_initvals_0_1); - break; - - default: - dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); - return -ENODEV; - } - - err = rtl8367_write_initvals(smi, initvals, count); - if (err) - return err; - - /* TODO: complete this */ - - return 0; -} - -static int rtl8367_init_regs1(struct rtl8366_smi *smi, unsigned mode) -{ - const struct rtl8367_initval *initvals; - int count; - - switch (mode) { - case 0: - initvals = rtl8367_initvals_1_0; - count = ARRAY_SIZE(rtl8367_initvals_1_0); - break; - - case 1: - case 2: - initvals = rtl8367_initvals_1_1; - count = ARRAY_SIZE(rtl8367_initvals_1_1); - break; - - default: - dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); - return -ENODEV; - } - - return rtl8367_write_initvals(smi, initvals, count); -} - -static int rtl8367_init_regs2(struct rtl8366_smi *smi, unsigned mode) -{ - const struct rtl8367_initval *initvals; - int count; - - switch (mode) { - case 0: - initvals = rtl8367_initvals_2_0; - count = ARRAY_SIZE(rtl8367_initvals_2_0); - break; - - case 1: - case 2: - initvals = rtl8367_initvals_2_1; - count = ARRAY_SIZE(rtl8367_initvals_2_1); - break; - - default: - dev_err(smi->parent, "%s: unknow mode %u\n", __func__, mode); - return -ENODEV; - } - - return rtl8367_write_initvals(smi, initvals, count); -} - -static int rtl8367_init_regs(struct rtl8366_smi *smi) -{ - u32 data; - u32 rlvid; - u32 mode; - int err; - - REG_WR(smi, RTL8367_RTL_MAGIC_ID_REG, RTL8367_RTL_MAGIC_ID_VAL); - - REG_RD(smi, RTL8367_CHIP_VER_REG, &data); - rlvid = (data >> RTL8367_CHIP_VER_RLVID_SHIFT) & - RTL8367_CHIP_VER_RLVID_MASK; - - REG_RD(smi, RTL8367_CHIP_MODE_REG, &data); - mode = data & RTL8367_CHIP_MODE_MASK; - - switch (rlvid) { - case 0: - err = rtl8367_init_regs0(smi, mode); - break; - - case 1: - err = rtl8367_write_phy_reg(smi, 0, 31, 5); - if (err) - break; - - err = rtl8367_write_phy_reg(smi, 0, 5, 0x3ffe); - if (err) - break; - - err = rtl8367_read_phy_reg(smi, 0, 6, &data); - if (err) - break; - - if (data == 0x94eb) { - err = rtl8367_init_regs1(smi, mode); - } else if (data == 0x2104) { - err = rtl8367_init_regs2(smi, mode); - } else { - dev_err(smi->parent, "unknow phy data %04x\n", data); - return -ENODEV; - } - - break; - - default: - dev_err(smi->parent, "unknow rlvid %u\n", rlvid); - err = -ENODEV; - break; - } - - return err; -} - -static int rtl8367_reset_chip(struct rtl8366_smi *smi) -{ - int timeout = 10; - int err; - u32 data; - - REG_WR(smi, RTL8367_CHIP_RESET_REG, RTL8367_CHIP_RESET_HW); - msleep(RTL8367_RESET_DELAY); - - do { - REG_RD(smi, RTL8367_CHIP_RESET_REG, &data); - if (!(data & RTL8367_CHIP_RESET_HW)) - break; - - msleep(1); - } while (--timeout); - - if (!timeout) { - dev_err(smi->parent, "chip reset timed out\n"); - return -ETIMEDOUT; - } - - return 0; -} - -static int rtl8367_extif_set_mode(struct rtl8366_smi *smi, int id, - enum rtl8367_extif_mode mode) -{ - int err; - - /* set port mode */ - switch (mode) { - case RTL8367_EXTIF_MODE_RGMII: - case RTL8367_EXTIF_MODE_RGMII_33V: - REG_WR(smi, RTL8367_CHIP_DEBUG0_REG, 0x0367); - REG_WR(smi, RTL8367_CHIP_DEBUG1_REG, 0x7777); - break; - - case RTL8367_EXTIF_MODE_TMII_MAC: - case RTL8367_EXTIF_MODE_TMII_PHY: - REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, - BIT((id + 1) % 2), BIT((id + 1) % 2)); - break; - - case RTL8367_EXTIF_MODE_GMII: - REG_RMW(smi, RTL8367_CHIP_DEBUG0_REG, - RTL8367_CHIP_DEBUG0_DUMMY0(id), - RTL8367_CHIP_DEBUG0_DUMMY0(id)); - REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), BIT(6)); - break; - - case RTL8367_EXTIF_MODE_MII_MAC: - case RTL8367_EXTIF_MODE_MII_PHY: - case RTL8367_EXTIF_MODE_DISABLED: - REG_RMW(smi, RTL8367_BYPASS_LINE_RATE_REG, - BIT((id + 1) % 2), 0); - REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), BIT(6), 0); - break; - - default: - dev_err(smi->parent, - "invalid mode for external interface %d\n", id); - return -EINVAL; - } - - REG_RMW(smi, RTL8367_DIS_REG, - RTL8367_DIS_RGMII_MASK << RTL8367_DIS_RGMII_SHIFT(id), - mode << RTL8367_DIS_RGMII_SHIFT(id)); - - return 0; -} - -static int rtl8367_extif_set_force(struct rtl8366_smi *smi, int id, - struct rtl8367_port_ability *pa) -{ - u32 mask; - u32 val; - int err; - - mask = (RTL8367_DI_FORCE_MODE | - RTL8367_DI_FORCE_NWAY | - RTL8367_DI_FORCE_TXPAUSE | - RTL8367_DI_FORCE_RXPAUSE | - RTL8367_DI_FORCE_LINK | - RTL8367_DI_FORCE_DUPLEX | - RTL8367_DI_FORCE_SPEED_MASK); - - val = pa->speed; - val |= pa->force_mode ? RTL8367_DI_FORCE_MODE : 0; - val |= pa->nway ? RTL8367_DI_FORCE_NWAY : 0; - val |= pa->txpause ? RTL8367_DI_FORCE_TXPAUSE : 0; - val |= pa->rxpause ? RTL8367_DI_FORCE_RXPAUSE : 0; - val |= pa->link ? RTL8367_DI_FORCE_LINK : 0; - val |= pa->duplex ? RTL8367_DI_FORCE_DUPLEX : 0; - - REG_RMW(smi, RTL8367_DI_FORCE_REG(id), mask, val); - - return 0; -} - -static int rtl8367_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, - unsigned txdelay, unsigned rxdelay) -{ - u32 mask; - u32 val; - int err; - - mask = (RTL8367_EXT_RGMXF_RXDELAY_MASK | - (RTL8367_EXT_RGMXF_TXDELAY_MASK << - RTL8367_EXT_RGMXF_TXDELAY_SHIFT)); - - val = rxdelay; - val |= txdelay << RTL8367_EXT_RGMXF_TXDELAY_SHIFT; - - REG_RMW(smi, RTL8367_EXT_RGMXF_REG(id), mask, val); - - return 0; -} - -static int rtl8367_extif_init(struct rtl8366_smi *smi, int id, - struct rtl8367_extif_config *cfg) -{ - enum rtl8367_extif_mode mode; - int err; - - mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; - - err = rtl8367_extif_set_mode(smi, id, mode); - if (err) - return err; - - if (mode != RTL8367_EXTIF_MODE_DISABLED) { - err = rtl8367_extif_set_force(smi, id, &cfg->ability); - if (err) - return err; - - err = rtl8367_extif_set_rgmii_delay(smi, id, cfg->txdelay, - cfg->rxdelay); - if (err) - return err; - } - - return 0; -} - -static int rtl8367_led_group_set_ports(struct rtl8366_smi *smi, - unsigned int group, u16 port_mask) -{ - u32 reg; - u32 s; - int err; - - port_mask &= RTL8367_PARA_LED_IO_EN_PMASK; - s = (group % 2) * 8; - reg = RTL8367_PARA_LED_IO_EN1_REG + (group / 2); - - REG_RMW(smi, reg, (RTL8367_PARA_LED_IO_EN_PMASK << s), port_mask << s); - - return 0; -} - -static int rtl8367_led_group_set_mode(struct rtl8366_smi *smi, - unsigned int mode) -{ - u16 mask; - u16 set; - int err; - - mode &= RTL8367_LED_CONFIG_DATA_M; - - mask = (RTL8367_LED_CONFIG_DATA_M << RTL8367_LED_CONFIG_DATA_S) | - RTL8367_LED_CONFIG_SEL; - set = (mode << RTL8367_LED_CONFIG_DATA_S) | RTL8367_LED_CONFIG_SEL; - - REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); - - return 0; -} - -static int rtl8367_led_group_set_config(struct rtl8366_smi *smi, - unsigned int led, unsigned int cfg) -{ - u16 mask; - u16 set; - int err; - - mask = (RTL8367_LED_CONFIG_LED_CFG_M << (led * 4)) | - RTL8367_LED_CONFIG_SEL; - set = (cfg & RTL8367_LED_CONFIG_LED_CFG_M) << (led * 4); - - REG_RMW(smi, RTL8367_LED_CONFIG_REG, mask, set); - return 0; -} - -static int rtl8367_led_op_select_parallel(struct rtl8366_smi *smi) -{ - int err; - - REG_WR(smi, RTL8367_LED_SYS_CONFIG_REG, 0x1472); - return 0; -} - -static int rtl8367_led_blinkrate_set(struct rtl8366_smi *smi, unsigned int rate) -{ - u16 mask; - u16 set; - int err; - - mask = RTL8367_LED_MODE_RATE_M << RTL8367_LED_MODE_RATE_S; - set = (rate & RTL8367_LED_MODE_RATE_M) << RTL8367_LED_MODE_RATE_S; - REG_RMW(smi, RTL8367_LED_MODE_REG, mask, set); - - return 0; -} - -#ifdef CONFIG_OF -static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, - const char *name) -{ - struct rtl8367_extif_config *cfg; - const __be32 *prop; - int size; - int err; - - prop = of_get_property(smi->parent->of_node, name, &size); - if (!prop) - return rtl8367_extif_init(smi, id, NULL); - - if (size != (9 * sizeof(*prop))) { - dev_err(smi->parent, "%s property is invalid\n", name); - return -EINVAL; - } - - cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); - if (!cfg) - return -ENOMEM; - - cfg->txdelay = be32_to_cpup(prop++); - cfg->rxdelay = be32_to_cpup(prop++); - cfg->mode = be32_to_cpup(prop++); - cfg->ability.force_mode = be32_to_cpup(prop++); - cfg->ability.txpause = be32_to_cpup(prop++); - cfg->ability.rxpause = be32_to_cpup(prop++); - cfg->ability.link = be32_to_cpup(prop++); - cfg->ability.duplex = be32_to_cpup(prop++); - cfg->ability.speed = be32_to_cpup(prop++); - - err = rtl8367_extif_init(smi, id, cfg); - kfree(cfg); - - return err; -} -#else -static int rtl8367_extif_init_of(struct rtl8366_smi *smi, int id, - const char *name) -{ - return -EINVAL; -} -#endif - -static int rtl8367_setup(struct rtl8366_smi *smi) -{ - struct rtl8367_platform_data *pdata; - int err; - int i; - - pdata = smi->parent->platform_data; - - err = rtl8367_init_regs(smi); - if (err) - return err; - - /* initialize external interfaces */ - if (smi->parent->of_node) { - err = rtl8367_extif_init_of(smi, 0, "realtek,extif0"); - if (err) - return err; - - err = rtl8367_extif_init_of(smi, 1, "realtek,extif1"); - if (err) - return err; - } else { - err = rtl8367_extif_init(smi, 0, pdata->extif0_cfg); - if (err) - return err; - - err = rtl8367_extif_init(smi, 1, pdata->extif1_cfg); - if (err) - return err; - } - - /* set maximum packet length to 1536 bytes */ - REG_RMW(smi, RTL8367_SWC0_REG, RTL8367_SWC0_MAX_LENGTH_MASK, - RTL8367_SWC0_MAX_LENGTH_1536); - - /* - * discard VLAN tagged packets if the port is not a member of - * the VLAN with which the packets is associated. - */ - REG_WR(smi, RTL8367_VLAN_INGRESS_REG, RTL8367_PORTS_ALL); - - /* - * Setup egress tag mode for each port. - */ - for (i = 0; i < RTL8367_NUM_PORTS; i++) - REG_RMW(smi, - RTL8367_PORT_CFG_REG(i), - RTL8367_PORT_CFG_EGRESS_MODE_MASK << - RTL8367_PORT_CFG_EGRESS_MODE_SHIFT, - RTL8367_PORT_CFG_EGRESS_MODE_ORIGINAL << - RTL8367_PORT_CFG_EGRESS_MODE_SHIFT); - - /* setup LEDs */ - err = rtl8367_led_group_set_ports(smi, 0, RTL8367_PORTS_ALL); - if (err) - return err; - - err = rtl8367_led_group_set_mode(smi, 0); - if (err) - return err; - - err = rtl8367_led_op_select_parallel(smi); - if (err) - return err; - - err = rtl8367_led_blinkrate_set(smi, 1); - if (err) - return err; - - err = rtl8367_led_group_set_config(smi, 0, 2); - if (err) - return err; - - return 0; -} - -static int rtl8367_get_mib_counter(struct rtl8366_smi *smi, int counter, - int port, unsigned long long *val) -{ - struct rtl8366_mib_counter *mib; - int offset; - int i; - int err; - u32 addr, data; - u64 mibvalue; - - if (port > RTL8367_NUM_PORTS || counter >= RTL8367_MIB_COUNT) - return -EINVAL; - - mib = &rtl8367_mib_counters[counter]; - addr = RTL8367_MIB_COUNTER_PORT_OFFSET * port + mib->offset; - - /* - * Writing access counter address first - * then ASIC will prepare 64bits counter wait for being retrived - */ - REG_WR(smi, RTL8367_MIB_ADDRESS_REG, addr >> 2); - - /* read MIB control register */ - REG_RD(smi, RTL8367_MIB_CTRL_REG(0), &data); - - if (data & RTL8367_MIB_CTRL_BUSY_MASK) - return -EBUSY; - - if (data & RTL8367_MIB_CTRL_RESET_MASK) - return -EIO; - - if (mib->length == 4) - offset = 3; - else - offset = (mib->offset + 1) % 4; - - mibvalue = 0; - for (i = 0; i < mib->length; i++) { - REG_RD(smi, RTL8367_MIB_COUNTER_REG(offset - i), &data); - mibvalue = (mibvalue << 16) | (data & 0xFFFF); - } - - *val = mibvalue; - return 0; -} - -static int rtl8367_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, - struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[RTL8367_TA_VLAN_DATA_SIZE]; - int err; - int i; - - memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); - - if (vid >= RTL8367_NUM_VIDS) - return -EINVAL; - - /* write VID */ - REG_WR(smi, RTL8367_TA_ADDR_REG, vid); - - /* write table access control word */ - REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_READ); - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_RD(smi, RTL8367_TA_DATA_REG(i), &data[i]); - - vlan4k->vid = vid; - vlan4k->member = (data[0] >> RTL8367_TA_VLAN_MEMBER_SHIFT) & - RTL8367_TA_VLAN_MEMBER_MASK; - vlan4k->fid = (data[1] >> RTL8367_TA_VLAN_FID_SHIFT) & - RTL8367_TA_VLAN_FID_MASK; - vlan4k->untag = (data[2] >> RTL8367_TA_VLAN_UNTAG1_SHIFT) & - RTL8367_TA_VLAN_UNTAG1_MASK; - vlan4k->untag |= ((data[3] >> RTL8367_TA_VLAN_UNTAG2_SHIFT) & - RTL8367_TA_VLAN_UNTAG2_MASK) << 2; - - return 0; -} - -static int rtl8367_set_vlan_4k(struct rtl8366_smi *smi, - const struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[RTL8367_TA_VLAN_DATA_SIZE]; - int err; - int i; - - if (vlan4k->vid >= RTL8367_NUM_VIDS || - vlan4k->member > RTL8367_TA_VLAN_MEMBER_MASK || - vlan4k->untag > RTL8367_UNTAG_MASK || - vlan4k->fid > RTL8367_FIDMAX) - return -EINVAL; - - data[0] = (vlan4k->member & RTL8367_TA_VLAN_MEMBER_MASK) << - RTL8367_TA_VLAN_MEMBER_SHIFT; - data[1] = (vlan4k->fid & RTL8367_TA_VLAN_FID_MASK) << - RTL8367_TA_VLAN_FID_SHIFT; - data[2] = (vlan4k->untag & RTL8367_TA_VLAN_UNTAG1_MASK) << - RTL8367_TA_VLAN_UNTAG1_SHIFT; - data[3] = ((vlan4k->untag >> 2) & RTL8367_TA_VLAN_UNTAG2_MASK) << - RTL8367_TA_VLAN_UNTAG2_SHIFT; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_WR(smi, RTL8367_TA_DATA_REG(i), data[i]); - - /* write VID */ - REG_WR(smi, RTL8367_TA_ADDR_REG, - vlan4k->vid & RTL8367_TA_VLAN_VID_MASK); - - /* write table access control word */ - REG_WR(smi, RTL8367_TA_CTRL_REG, RTL8367_TA_CTRL_CVLAN_WRITE); - - return 0; -} - -static int rtl8367_get_vlan_mc(struct rtl8366_smi *smi, u32 index, - struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[RTL8367_VLAN_MC_DATA_SIZE]; - int err; - int i; - - memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); - - if (index >= RTL8367_NUM_VLANS) - return -EINVAL; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_RD(smi, RTL8367_VLAN_MC_BASE(index) + i, &data[i]); - - vlanmc->member = (data[0] >> RTL8367_VLAN_MC_MEMBER_SHIFT) & - RTL8367_VLAN_MC_MEMBER_MASK; - vlanmc->fid = (data[1] >> RTL8367_VLAN_MC_FID_SHIFT) & - RTL8367_VLAN_MC_FID_MASK; - vlanmc->vid = (data[3] >> RTL8367_VLAN_MC_EVID_SHIFT) & - RTL8367_VLAN_MC_EVID_MASK; - - return 0; -} - -static int rtl8367_set_vlan_mc(struct rtl8366_smi *smi, u32 index, - const struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[RTL8367_VLAN_MC_DATA_SIZE]; - int err; - int i; - - if (index >= RTL8367_NUM_VLANS || - vlanmc->vid >= RTL8367_NUM_VIDS || - vlanmc->priority > RTL8367_PRIORITYMAX || - vlanmc->member > RTL8367_VLAN_MC_MEMBER_MASK || - vlanmc->untag > RTL8367_UNTAG_MASK || - vlanmc->fid > RTL8367_FIDMAX) - return -EINVAL; - - data[0] = (vlanmc->member & RTL8367_VLAN_MC_MEMBER_MASK) << - RTL8367_VLAN_MC_MEMBER_SHIFT; - data[1] = (vlanmc->fid & RTL8367_VLAN_MC_FID_MASK) << - RTL8367_VLAN_MC_FID_SHIFT; - data[2] = 0; - data[3] = (vlanmc->vid & RTL8367_VLAN_MC_EVID_MASK) << - RTL8367_VLAN_MC_EVID_SHIFT; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_WR(smi, RTL8367_VLAN_MC_BASE(index) + i, data[i]); - - return 0; -} - -static int rtl8367_get_mc_index(struct rtl8366_smi *smi, int port, int *val) -{ - u32 data; - int err; - - if (port >= RTL8367_NUM_PORTS) - return -EINVAL; - - REG_RD(smi, RTL8367_VLAN_PVID_CTRL_REG(port), &data); - - *val = (data >> RTL8367_VLAN_PVID_CTRL_SHIFT(port)) & - RTL8367_VLAN_PVID_CTRL_MASK; - - return 0; -} - -static int rtl8367_set_mc_index(struct rtl8366_smi *smi, int port, int index) -{ - if (port >= RTL8367_NUM_PORTS || index >= RTL8367_NUM_VLANS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8367_VLAN_PVID_CTRL_REG(port), - RTL8367_VLAN_PVID_CTRL_MASK << - RTL8367_VLAN_PVID_CTRL_SHIFT(port), - (index & RTL8367_VLAN_PVID_CTRL_MASK) << - RTL8367_VLAN_PVID_CTRL_SHIFT(port)); -} - -static int rtl8367_enable_vlan(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8367_VLAN_CTRL_REG, - RTL8367_VLAN_CTRL_ENABLE, - (enable) ? RTL8367_VLAN_CTRL_ENABLE : 0); -} - -static int rtl8367_enable_vlan4k(struct rtl8366_smi *smi, int enable) -{ - return 0; -} - -static int rtl8367_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) -{ - unsigned max = RTL8367_NUM_VLANS; - - if (smi->vlan4k_enabled) - max = RTL8367_NUM_VIDS - 1; - - if (vlan == 0 || vlan >= max) - return 0; - - return 1; -} - -static int rtl8367_enable_port(struct rtl8366_smi *smi, int port, int enable) -{ - int err; - - REG_WR(smi, RTL8367_PORT_ISOLATION_REG(port), - (enable) ? RTL8367_PORTS_ALL : 0); - - return 0; -} - -static int rtl8367_sw_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(0), 0, - RTL8367_MIB_CTRL_GLOBAL_RESET_MASK); -} - -static int rtl8367_sw_get_port_link(struct switch_dev *dev, - int port, - struct switch_port_link *link) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - u32 speed; - - if (port >= RTL8367_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8367_PORT_STATUS_REG(port), &data); - - link->link = !!(data & RTL8367_PORT_STATUS_LINK); - if (!link->link) - return 0; - - link->duplex = !!(data & RTL8367_PORT_STATUS_DUPLEX); - link->rx_flow = !!(data & RTL8367_PORT_STATUS_RXPAUSE); - link->tx_flow = !!(data & RTL8367_PORT_STATUS_TXPAUSE); - link->aneg = !!(data & RTL8367_PORT_STATUS_NWAY); - - speed = (data & RTL8367_PORT_STATUS_SPEED_MASK); - switch (speed) { - case 0: - link->speed = SWITCH_PORT_SPEED_10; - break; - case 1: - link->speed = SWITCH_PORT_SPEED_100; - break; - case 2: - link->speed = SWITCH_PORT_SPEED_1000; - break; - default: - link->speed = SWITCH_PORT_SPEED_UNKNOWN; - break; - } - - return 0; -} - -static int rtl8367_sw_get_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8367_SWC0_REG, &data); - val->value.i = (data & RTL8367_SWC0_MAX_LENGTH_MASK) >> - RTL8367_SWC0_MAX_LENGTH_SHIFT; - - return 0; -} - -static int rtl8367_sw_set_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 max_len; - - switch (val->value.i) { - case 0: - max_len = RTL8367_SWC0_MAX_LENGTH_1522; - break; - case 1: - max_len = RTL8367_SWC0_MAX_LENGTH_1536; - break; - case 2: - max_len = RTL8367_SWC0_MAX_LENGTH_1552; - break; - case 3: - max_len = RTL8367_SWC0_MAX_LENGTH_16000; - break; - default: - return -EINVAL; - } - - return rtl8366_smi_rmwr(smi, RTL8367_SWC0_REG, - RTL8367_SWC0_MAX_LENGTH_MASK, max_len); -} - - -static int rtl8367_sw_reset_port_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int port; - - port = val->port_vlan; - if (port >= RTL8367_NUM_PORTS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8367_MIB_CTRL_REG(port / 8), 0, - RTL8367_MIB_CTRL_PORT_RESET_MASK(port % 8)); -} - -static int rtl8367_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats) -{ - return (rtl8366_sw_get_port_stats(dev, port, stats, - RTL8367_MIB_TXB_ID, RTL8367_MIB_RXB_ID)); -} - -static struct switch_attr rtl8367_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan4k", - .description = "Enable VLAN 4K mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 2 - }, { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = rtl8367_sw_reset_mibs, - }, { - .type = SWITCH_TYPE_INT, - .name = "max_length", - .description = "Get/Set the maximum length of valid packets" - "(0:1522, 1:1536, 2:1552, 3:16000)", - .set = rtl8367_sw_set_max_length, - .get = rtl8367_sw_get_max_length, - .max = 3, - } -}; - -static struct switch_attr rtl8367_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = rtl8367_sw_reset_port_mibs, - }, { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get MIB counters for port", - .max = 33, - .set = NULL, - .get = rtl8366_sw_get_port_mib, - }, -}; - -static struct switch_attr rtl8367_vlan[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "info", - .description = "Get vlan information", - .max = 1, - .set = NULL, - .get = rtl8366_sw_get_vlan_info, - }, { - .type = SWITCH_TYPE_INT, - .name = "fid", - .description = "Get/Set vlan FID", - .max = RTL8367_FIDMAX, - .set = rtl8366_sw_set_vlan_fid, - .get = rtl8366_sw_get_vlan_fid, - }, -}; - -static const struct switch_dev_ops rtl8367_sw_ops = { - .attr_global = { - .attr = rtl8367_globals, - .n_attr = ARRAY_SIZE(rtl8367_globals), - }, - .attr_port = { - .attr = rtl8367_port, - .n_attr = ARRAY_SIZE(rtl8367_port), - }, - .attr_vlan = { - .attr = rtl8367_vlan, - .n_attr = ARRAY_SIZE(rtl8367_vlan), - }, - - .get_vlan_ports = rtl8366_sw_get_vlan_ports, - .set_vlan_ports = rtl8366_sw_set_vlan_ports, - .get_port_pvid = rtl8366_sw_get_port_pvid, - .set_port_pvid = rtl8366_sw_set_port_pvid, - .reset_switch = rtl8366_sw_reset_switch, - .get_port_link = rtl8367_sw_get_port_link, - .get_port_stats = rtl8367_sw_get_port_stats, -}; - -static int rtl8367_switch_init(struct rtl8366_smi *smi) -{ - struct switch_dev *dev = &smi->sw_dev; - int err; - - dev->name = "RTL8367"; - dev->cpu_port = RTL8367_CPU_PORT_NUM; - dev->ports = RTL8367_NUM_PORTS; - dev->vlans = RTL8367_NUM_VIDS; - dev->ops = &rtl8367_sw_ops; - dev->alias = dev_name(smi->parent); - - err = register_switch(dev, NULL); - if (err) - dev_err(smi->parent, "switch registration failed\n"); - - return err; -} - -static void rtl8367_switch_cleanup(struct rtl8366_smi *smi) -{ - unregister_switch(&smi->sw_dev); -} - -static int rtl8367_mii_read(struct mii_bus *bus, int addr, int reg) -{ - struct rtl8366_smi *smi = bus->priv; - u32 val = 0; - int err; - - err = rtl8367_read_phy_reg(smi, addr, reg, &val); - if (err) - return 0xffff; - - return val; -} - -static int rtl8367_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) -{ - struct rtl8366_smi *smi = bus->priv; - u32 t; - int err; - - err = rtl8367_write_phy_reg(smi, addr, reg, val); - if (err) - return err; - - /* flush write */ - (void) rtl8367_read_phy_reg(smi, addr, reg, &t); - - return err; -} - -static int rtl8367_detect(struct rtl8366_smi *smi) -{ - u32 rtl_no = 0; - u32 rtl_ver = 0; - char *chip_name; - int ret; - - ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_NO_REG, &rtl_no); - if (ret) { - dev_err(smi->parent, "unable to read chip number\n"); - return ret; - } - - switch (rtl_no) { - case RTL8367_RTL_NO_8367R: - chip_name = "8367R"; - break; - case RTL8367_RTL_NO_8367M: - chip_name = "8367M"; - break; - default: - dev_err(smi->parent, "unknown chip number (%04x)\n", rtl_no); - return -ENODEV; - } - - ret = rtl8366_smi_read_reg(smi, RTL8367_RTL_VER_REG, &rtl_ver); - if (ret) { - dev_err(smi->parent, "unable to read chip version\n"); - return ret; - } - - dev_info(smi->parent, "RTL%s ver. %u chip found\n", - chip_name, rtl_ver & RTL8367_RTL_VER_MASK); - - return 0; -} - -static struct rtl8366_smi_ops rtl8367_smi_ops = { - .detect = rtl8367_detect, - .reset_chip = rtl8367_reset_chip, - .setup = rtl8367_setup, - - .mii_read = rtl8367_mii_read, - .mii_write = rtl8367_mii_write, - - .get_vlan_mc = rtl8367_get_vlan_mc, - .set_vlan_mc = rtl8367_set_vlan_mc, - .get_vlan_4k = rtl8367_get_vlan_4k, - .set_vlan_4k = rtl8367_set_vlan_4k, - .get_mc_index = rtl8367_get_mc_index, - .set_mc_index = rtl8367_set_mc_index, - .get_mib_counter = rtl8367_get_mib_counter, - .is_vlan_valid = rtl8367_is_vlan_valid, - .enable_vlan = rtl8367_enable_vlan, - .enable_vlan4k = rtl8367_enable_vlan4k, - .enable_port = rtl8367_enable_port, -}; - -static int rtl8367_probe(struct platform_device *pdev) -{ - struct rtl8366_smi *smi; - int err; - - smi = rtl8366_smi_probe(pdev); - if (!smi) - return -ENODEV; - - smi->clk_delay = 1500; - smi->cmd_read = 0xb9; - smi->cmd_write = 0xb8; - smi->ops = &rtl8367_smi_ops; - smi->cpu_port = RTL8367_CPU_PORT_NUM; - smi->num_ports = RTL8367_NUM_PORTS; - smi->num_vlan_mc = RTL8367_NUM_VLANS; - smi->mib_counters = rtl8367_mib_counters; - smi->num_mib_counters = ARRAY_SIZE(rtl8367_mib_counters); - - err = rtl8366_smi_init(smi); - if (err) - goto err_free_smi; - - platform_set_drvdata(pdev, smi); - - err = rtl8367_switch_init(smi); - if (err) - goto err_clear_drvdata; - - return 0; - - err_clear_drvdata: - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - err_free_smi: - kfree(smi); - return err; -} - -static int rtl8367_remove(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) { - rtl8367_switch_cleanup(smi); - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - kfree(smi); - } - - return 0; -} - -static void rtl8367_shutdown(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) - rtl8367_reset_chip(smi); -} - -#ifdef CONFIG_OF -static const struct of_device_id rtl8367_match[] = { - { .compatible = "realtek,rtl8367" }, - {}, -}; -MODULE_DEVICE_TABLE(of, rtl8367_match); -#endif - -static struct platform_driver rtl8367_driver = { - .driver = { - .name = RTL8367_DRIVER_NAME, - .owner = THIS_MODULE, -#ifdef CONFIG_OF - .of_match_table = of_match_ptr(rtl8367_match), -#endif - }, - .probe = rtl8367_probe, - .remove = rtl8367_remove, - .shutdown = rtl8367_shutdown, -}; - -static int __init rtl8367_module_init(void) -{ - return platform_driver_register(&rtl8367_driver); -} -module_init(rtl8367_module_init); - -static void __exit rtl8367_module_exit(void) -{ - platform_driver_unregister(&rtl8367_driver); -} -module_exit(rtl8367_module_exit); - -MODULE_DESCRIPTION("Realtek RTL8367 ethernet switch driver"); -MODULE_AUTHOR("Gabor Juhos "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME); diff --git a/target/linux/generic/files/drivers/net/phy/rtl8367b.c b/target/linux/generic/files/drivers/net/phy/rtl8367b.c deleted file mode 100644 index e6ea6509ae..0000000000 --- a/target/linux/generic/files/drivers/net/phy/rtl8367b.c +++ /dev/null @@ -1,1612 +0,0 @@ -/* - * Platform driver for the Realtek RTL8367R-VB ethernet switches - * - * Copyright (C) 2012 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "rtl8366_smi.h" - -#define RTL8367B_RESET_DELAY 1000 /* msecs*/ - -#define RTL8367B_PHY_ADDR_MAX 8 -#define RTL8367B_PHY_REG_MAX 31 - -#define RTL8367B_VID_MASK 0x3fff -#define RTL8367B_FID_MASK 0xf -#define RTL8367B_UNTAG_MASK 0xff -#define RTL8367B_MEMBER_MASK 0xff - -#define RTL8367B_PORT_MISC_CFG_REG(_p) (0x000e + 0x20 * (_p)) -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT 4 -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK 0x3 -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL 0 -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_KEEP 1 -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_PRI 2 -#define RTL8367B_PORT_MISC_CFG_EGRESS_MODE_REAL 3 - -#define RTL8367B_BYPASS_LINE_RATE_REG 0x03f7 - -#define RTL8367B_TA_CTRL_REG 0x0500 /*GOOD*/ -#define RTL8367B_TA_CTRL_SPA_SHIFT 8 -#define RTL8367B_TA_CTRL_SPA_MASK 0x7 -#define RTL8367B_TA_CTRL_METHOD BIT(4)/*GOOD*/ -#define RTL8367B_TA_CTRL_CMD_SHIFT 3 -#define RTL8367B_TA_CTRL_CMD_READ 0 -#define RTL8367B_TA_CTRL_CMD_WRITE 1 -#define RTL8367B_TA_CTRL_TABLE_SHIFT 0 /*GOOD*/ -#define RTL8367B_TA_CTRL_TABLE_ACLRULE 1 -#define RTL8367B_TA_CTRL_TABLE_ACLACT 2 -#define RTL8367B_TA_CTRL_TABLE_CVLAN 3 -#define RTL8367B_TA_CTRL_TABLE_L2 4 -#define RTL8367B_TA_CTRL_CVLAN_READ \ - ((RTL8367B_TA_CTRL_CMD_READ << RTL8367B_TA_CTRL_CMD_SHIFT) | \ - RTL8367B_TA_CTRL_TABLE_CVLAN) -#define RTL8367B_TA_CTRL_CVLAN_WRITE \ - ((RTL8367B_TA_CTRL_CMD_WRITE << RTL8367B_TA_CTRL_CMD_SHIFT) | \ - RTL8367B_TA_CTRL_TABLE_CVLAN) - -#define RTL8367B_TA_ADDR_REG 0x0501/*GOOD*/ -#define RTL8367B_TA_ADDR_MASK 0x3fff/*GOOD*/ - -#define RTL8367B_TA_LUT_REG 0x0502/*GOOD*/ - -#define RTL8367B_TA_WRDATA_REG(_x) (0x0510 + (_x))/*GOOD*/ -#define RTL8367B_TA_VLAN_NUM_WORDS 2 -#define RTL8367B_TA_VLAN_VID_MASK RTL8367B_VID_MASK -#define RTL8367B_TA_VLAN0_MEMBER_SHIFT 0 -#define RTL8367B_TA_VLAN0_MEMBER_MASK RTL8367B_MEMBER_MASK -#define RTL8367B_TA_VLAN0_UNTAG_SHIFT 8 -#define RTL8367B_TA_VLAN0_UNTAG_MASK RTL8367B_MEMBER_MASK -#define RTL8367B_TA_VLAN1_FID_SHIFT 0 -#define RTL8367B_TA_VLAN1_FID_MASK RTL8367B_FID_MASK - -#define RTL8367B_TA_RDDATA_REG(_x) (0x0520 + (_x))/*GOOD*/ - -#define RTL8367B_VLAN_PVID_CTRL_REG(_p) (0x0700 + (_p) / 2) /*GOOD*/ -#define RTL8367B_VLAN_PVID_CTRL_MASK 0x1f /*GOOD*/ -#define RTL8367B_VLAN_PVID_CTRL_SHIFT(_p) (8 * ((_p) % 2)) /*GOOD*/ - -#define RTL8367B_VLAN_MC_BASE(_x) (0x0728 + (_x) * 4) /*GOOD*/ -#define RTL8367B_VLAN_MC_NUM_WORDS 4 /*GOOD*/ -#define RTL8367B_VLAN_MC0_MEMBER_SHIFT 0/*GOOD*/ -#define RTL8367B_VLAN_MC0_MEMBER_MASK RTL8367B_MEMBER_MASK/*GOOD*/ -#define RTL8367B_VLAN_MC1_FID_SHIFT 0/*GOOD*/ -#define RTL8367B_VLAN_MC1_FID_MASK RTL8367B_FID_MASK/*GOOD*/ -#define RTL8367B_VLAN_MC3_EVID_SHIFT 0/*GOOD*/ -#define RTL8367B_VLAN_MC3_EVID_MASK RTL8367B_VID_MASK/*GOOD*/ - -#define RTL8367B_VLAN_CTRL_REG 0x07a8 /*GOOD*/ -#define RTL8367B_VLAN_CTRL_ENABLE BIT(0) - -#define RTL8367B_VLAN_INGRESS_REG 0x07a9 /*GOOD*/ - -#define RTL8367B_PORT_ISOLATION_REG(_p) (0x08a2 + (_p)) /*GOOD*/ - -#define RTL8367B_MIB_COUNTER_REG(_x) (0x1000 + (_x)) /*GOOD*/ -#define RTL8367B_MIB_COUNTER_PORT_OFFSET 0x007c /*GOOD*/ - -#define RTL8367B_MIB_ADDRESS_REG 0x1004 /*GOOD*/ - -#define RTL8367B_MIB_CTRL0_REG(_x) (0x1005 + (_x)) /*GOOD*/ -#define RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK BIT(11) /*GOOD*/ -#define RTL8367B_MIB_CTRL0_QM_RESET_MASK BIT(10) /*GOOD*/ -#define RTL8367B_MIB_CTRL0_PORT_RESET_MASK(_p) BIT(2 + (_p)) /*GOOD*/ -#define RTL8367B_MIB_CTRL0_RESET_MASK BIT(1) /*GOOD*/ -#define RTL8367B_MIB_CTRL0_BUSY_MASK BIT(0) /*GOOD*/ - -#define RTL8367B_SWC0_REG 0x1200/*GOOD*/ -#define RTL8367B_SWC0_MAX_LENGTH_SHIFT 13/*GOOD*/ -#define RTL8367B_SWC0_MAX_LENGTH(_x) ((_x) << 13) /*GOOD*/ -#define RTL8367B_SWC0_MAX_LENGTH_MASK RTL8367B_SWC0_MAX_LENGTH(0x3) -#define RTL8367B_SWC0_MAX_LENGTH_1522 RTL8367B_SWC0_MAX_LENGTH(0) -#define RTL8367B_SWC0_MAX_LENGTH_1536 RTL8367B_SWC0_MAX_LENGTH(1) -#define RTL8367B_SWC0_MAX_LENGTH_1552 RTL8367B_SWC0_MAX_LENGTH(2) -#define RTL8367B_SWC0_MAX_LENGTH_16000 RTL8367B_SWC0_MAX_LENGTH(3) - -#define RTL8367B_CHIP_NUMBER_REG 0x1300/*GOOD*/ - -#define RTL8367B_CHIP_VER_REG 0x1301/*GOOD*/ -#define RTL8367B_CHIP_VER_RLVID_SHIFT 12/*GOOD*/ -#define RTL8367B_CHIP_VER_RLVID_MASK 0xf/*GOOD*/ -#define RTL8367B_CHIP_VER_MCID_SHIFT 8/*GOOD*/ -#define RTL8367B_CHIP_VER_MCID_MASK 0xf/*GOOD*/ -#define RTL8367B_CHIP_VER_BOID_SHIFT 4/*GOOD*/ -#define RTL8367B_CHIP_VER_BOID_MASK 0xf/*GOOD*/ -#define RTL8367B_CHIP_VER_AFE_SHIFT 0/*GOOD*/ -#define RTL8367B_CHIP_VER_AFE_MASK 0x1/*GOOD*/ - -#define RTL8367B_CHIP_MODE_REG 0x1302 -#define RTL8367B_CHIP_MODE_MASK 0x7 - -#define RTL8367B_CHIP_DEBUG0_REG 0x1303 -#define RTL8367B_CHIP_DEBUG0_DUMMY0(_x) BIT(8 + (_x)) - -#define RTL8367B_CHIP_DEBUG1_REG 0x1304 - -#define RTL8367B_DIS_REG 0x1305 -#define RTL8367B_DIS_SKIP_MII_RXER(_x) BIT(12 + (_x)) -#define RTL8367B_DIS_RGMII_SHIFT(_x) (4 * (_x)) -#define RTL8367B_DIS_RGMII_MASK 0x7 - -#define RTL8367B_EXT_RGMXF_REG(_x) (0x1306 + (_x)) -#define RTL8367B_EXT_RGMXF_DUMMY0_SHIFT 5 -#define RTL8367B_EXT_RGMXF_DUMMY0_MASK 0x7ff -#define RTL8367B_EXT_RGMXF_TXDELAY_SHIFT 3 -#define RTL8367B_EXT_RGMXF_TXDELAY_MASK 1 -#define RTL8367B_EXT_RGMXF_RXDELAY_MASK 0x7 - -#define RTL8367B_DI_FORCE_REG(_x) (0x1310 + (_x)) -#define RTL8367B_DI_FORCE_MODE BIT(12) -#define RTL8367B_DI_FORCE_NWAY BIT(7) -#define RTL8367B_DI_FORCE_TXPAUSE BIT(6) -#define RTL8367B_DI_FORCE_RXPAUSE BIT(5) -#define RTL8367B_DI_FORCE_LINK BIT(4) -#define RTL8367B_DI_FORCE_DUPLEX BIT(2) -#define RTL8367B_DI_FORCE_SPEED_MASK 3 -#define RTL8367B_DI_FORCE_SPEED_10 0 -#define RTL8367B_DI_FORCE_SPEED_100 1 -#define RTL8367B_DI_FORCE_SPEED_1000 2 - -#define RTL8367B_MAC_FORCE_REG(_x) (0x1312 + (_x)) - -#define RTL8367B_CHIP_RESET_REG 0x1322 /*GOOD*/ -#define RTL8367B_CHIP_RESET_SW BIT(1) /*GOOD*/ -#define RTL8367B_CHIP_RESET_HW BIT(0) /*GOOD*/ - -#define RTL8367B_PORT_STATUS_REG(_p) (0x1352 + (_p)) /*GOOD*/ -#define RTL8367B_PORT_STATUS_EN_1000_SPI BIT(11) /*GOOD*/ -#define RTL8367B_PORT_STATUS_EN_100_SPI BIT(10)/*GOOD*/ -#define RTL8367B_PORT_STATUS_NWAY_FAULT BIT(9)/*GOOD*/ -#define RTL8367B_PORT_STATUS_LINK_MASTER BIT(8)/*GOOD*/ -#define RTL8367B_PORT_STATUS_NWAY BIT(7)/*GOOD*/ -#define RTL8367B_PORT_STATUS_TXPAUSE BIT(6)/*GOOD*/ -#define RTL8367B_PORT_STATUS_RXPAUSE BIT(5)/*GOOD*/ -#define RTL8367B_PORT_STATUS_LINK BIT(4)/*GOOD*/ -#define RTL8367B_PORT_STATUS_DUPLEX BIT(2)/*GOOD*/ -#define RTL8367B_PORT_STATUS_SPEED_MASK 0x0003/*GOOD*/ -#define RTL8367B_PORT_STATUS_SPEED_10 0/*GOOD*/ -#define RTL8367B_PORT_STATUS_SPEED_100 1/*GOOD*/ -#define RTL8367B_PORT_STATUS_SPEED_1000 2/*GOOD*/ - -#define RTL8367B_RTL_MAGIC_ID_REG 0x13c2 -#define RTL8367B_RTL_MAGIC_ID_VAL 0x0249 - -#define RTL8367B_IA_CTRL_REG 0x1f00 -#define RTL8367B_IA_CTRL_RW(_x) ((_x) << 1) -#define RTL8367B_IA_CTRL_RW_READ RTL8367B_IA_CTRL_RW(0) -#define RTL8367B_IA_CTRL_RW_WRITE RTL8367B_IA_CTRL_RW(1) -#define RTL8367B_IA_CTRL_CMD_MASK BIT(0) - -#define RTL8367B_IA_STATUS_REG 0x1f01 -#define RTL8367B_IA_STATUS_PHY_BUSY BIT(2) -#define RTL8367B_IA_STATUS_SDS_BUSY BIT(1) -#define RTL8367B_IA_STATUS_MDX_BUSY BIT(0) - -#define RTL8367B_IA_ADDRESS_REG 0x1f02 -#define RTL8367B_IA_WRITE_DATA_REG 0x1f03 -#define RTL8367B_IA_READ_DATA_REG 0x1f04 - -#define RTL8367B_INTERNAL_PHY_REG(_a, _r) (0x2000 + 32 * (_a) + (_r)) - -#define RTL8367B_NUM_MIB_COUNTERS 58 - -#define RTL8367B_CPU_PORT_NUM 5 -#define RTL8367B_NUM_PORTS 8 -#define RTL8367B_NUM_VLANS 32 -#define RTL8367B_NUM_VIDS 4096 -#define RTL8367B_PRIORITYMAX 7 -#define RTL8367B_FIDMAX 7 - -#define RTL8367B_PORT_0 BIT(0) -#define RTL8367B_PORT_1 BIT(1) -#define RTL8367B_PORT_2 BIT(2) -#define RTL8367B_PORT_3 BIT(3) -#define RTL8367B_PORT_4 BIT(4) -#define RTL8367B_PORT_E0 BIT(5) /* External port 0 */ -#define RTL8367B_PORT_E1 BIT(6) /* External port 1 */ -#define RTL8367B_PORT_E2 BIT(7) /* External port 2 */ - -#define RTL8367B_PORTS_ALL \ - (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ - RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E0 | \ - RTL8367B_PORT_E1 | RTL8367B_PORT_E2) - -#define RTL8367B_PORTS_ALL_BUT_CPU \ - (RTL8367B_PORT_0 | RTL8367B_PORT_1 | RTL8367B_PORT_2 | \ - RTL8367B_PORT_3 | RTL8367B_PORT_4 | RTL8367B_PORT_E1 | \ - RTL8367B_PORT_E2) - -struct rtl8367b_initval { - u16 reg; - u16 val; -}; - -#define RTL8367B_MIB_RXB_ID 0 /* IfInOctets */ -#define RTL8367B_MIB_TXB_ID 28 /* IfOutOctets */ - -static struct rtl8366_mib_counter -rtl8367b_mib_counters[RTL8367B_NUM_MIB_COUNTERS] = { - {0, 0, 4, "ifInOctets" }, - {0, 4, 2, "dot3StatsFCSErrors" }, - {0, 6, 2, "dot3StatsSymbolErrors" }, - {0, 8, 2, "dot3InPauseFrames" }, - {0, 10, 2, "dot3ControlInUnknownOpcodes" }, - {0, 12, 2, "etherStatsFragments" }, - {0, 14, 2, "etherStatsJabbers" }, - {0, 16, 2, "ifInUcastPkts" }, - {0, 18, 2, "etherStatsDropEvents" }, - {0, 20, 2, "ifInMulticastPkts" }, - {0, 22, 2, "ifInBroadcastPkts" }, - {0, 24, 2, "inMldChecksumError" }, - {0, 26, 2, "inIgmpChecksumError" }, - {0, 28, 2, "inMldSpecificQuery" }, - {0, 30, 2, "inMldGeneralQuery" }, - {0, 32, 2, "inIgmpSpecificQuery" }, - {0, 34, 2, "inIgmpGeneralQuery" }, - {0, 36, 2, "inMldLeaves" }, - {0, 38, 2, "inIgmpLeaves" }, - - {0, 40, 4, "etherStatsOctets" }, - {0, 44, 2, "etherStatsUnderSizePkts" }, - {0, 46, 2, "etherOversizeStats" }, - {0, 48, 2, "etherStatsPkts64Octets" }, - {0, 50, 2, "etherStatsPkts65to127Octets" }, - {0, 52, 2, "etherStatsPkts128to255Octets" }, - {0, 54, 2, "etherStatsPkts256to511Octets" }, - {0, 56, 2, "etherStatsPkts512to1023Octets" }, - {0, 58, 2, "etherStatsPkts1024to1518Octets" }, - - {0, 60, 4, "ifOutOctets" }, - {0, 64, 2, "dot3StatsSingleCollisionFrames" }, - {0, 66, 2, "dot3StatMultipleCollisionFrames" }, - {0, 68, 2, "dot3sDeferredTransmissions" }, - {0, 70, 2, "dot3StatsLateCollisions" }, - {0, 72, 2, "etherStatsCollisions" }, - {0, 74, 2, "dot3StatsExcessiveCollisions" }, - {0, 76, 2, "dot3OutPauseFrames" }, - {0, 78, 2, "ifOutDiscards" }, - {0, 80, 2, "dot1dTpPortInDiscards" }, - {0, 82, 2, "ifOutUcastPkts" }, - {0, 84, 2, "ifOutMulticastPkts" }, - {0, 86, 2, "ifOutBroadcastPkts" }, - {0, 88, 2, "outOampduPkts" }, - {0, 90, 2, "inOampduPkts" }, - {0, 92, 2, "inIgmpJoinsSuccess" }, - {0, 94, 2, "inIgmpJoinsFail" }, - {0, 96, 2, "inMldJoinsSuccess" }, - {0, 98, 2, "inMldJoinsFail" }, - {0, 100, 2, "inReportSuppressionDrop" }, - {0, 102, 2, "inLeaveSuppressionDrop" }, - {0, 104, 2, "outIgmpReports" }, - {0, 106, 2, "outIgmpLeaves" }, - {0, 108, 2, "outIgmpGeneralQuery" }, - {0, 110, 2, "outIgmpSpecificQuery" }, - {0, 112, 2, "outMldReports" }, - {0, 114, 2, "outMldLeaves" }, - {0, 116, 2, "outMldGeneralQuery" }, - {0, 118, 2, "outMldSpecificQuery" }, - {0, 120, 2, "inKnownMulticastPkts" }, -}; - -#define REG_RD(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_read_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_WR(_smi, _reg, _val) \ - do { \ - err = rtl8366_smi_write_reg(_smi, _reg, _val); \ - if (err) \ - return err; \ - } while (0) - -#define REG_RMW(_smi, _reg, _mask, _val) \ - do { \ - err = rtl8366_smi_rmwr(_smi, _reg, _mask, _val); \ - if (err) \ - return err; \ - } while (0) - -static const struct rtl8367b_initval rtl8367r_vb_initvals_0[] = { - {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x0301, 0x0026}, {0x1722, 0x0E14}, - {0x205F, 0x0002}, {0x2059, 0x1A00}, {0x205F, 0x0000}, {0x207F, 0x0002}, - {0x2077, 0x0000}, {0x2078, 0x0000}, {0x2079, 0x0000}, {0x207A, 0x0000}, - {0x207B, 0x0000}, {0x207F, 0x0000}, {0x205F, 0x0002}, {0x2053, 0x0000}, - {0x2054, 0x0000}, {0x2055, 0x0000}, {0x2056, 0x0000}, {0x2057, 0x0000}, - {0x205F, 0x0000}, {0x12A4, 0x110A}, {0x12A6, 0x150A}, {0x13F1, 0x0013}, - {0x13F4, 0x0010}, {0x13F5, 0x0000}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, - {0x0058, 0x0F00}, {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x12B6, 0x0C02}, - {0x12B7, 0x030F}, {0x12B8, 0x11FF}, {0x12BC, 0x0004}, {0x1362, 0x0115}, - {0x1363, 0x0002}, {0x1363, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, - {0x221F, 0x0007}, {0x221E, 0x002D}, {0x2218, 0xF030}, {0x221F, 0x0007}, - {0x221E, 0x0023}, {0x2216, 0x0005}, {0x2215, 0x00B9}, {0x2219, 0x0044}, - {0x2215, 0x00BA}, {0x2219, 0x0020}, {0x2215, 0x00BB}, {0x2219, 0x00C1}, - {0x2215, 0x0148}, {0x2219, 0x0096}, {0x2215, 0x016E}, {0x2219, 0x0026}, - {0x2216, 0x0000}, {0x2216, 0x0000}, {0x221E, 0x002D}, {0x2218, 0xF010}, - {0x221F, 0x0007}, {0x221E, 0x0020}, {0x2215, 0x0D00}, {0x221F, 0x0000}, - {0x221F, 0x0000}, {0x2217, 0x2160}, {0x221F, 0x0001}, {0x2210, 0xF25E}, - {0x221F, 0x0007}, {0x221E, 0x0042}, {0x2215, 0x0F00}, {0x2215, 0x0F00}, - {0x2216, 0x7408}, {0x2215, 0x0E00}, {0x2215, 0x0F00}, {0x2215, 0x0F01}, - {0x2216, 0x4000}, {0x2215, 0x0E01}, {0x2215, 0x0F01}, {0x2215, 0x0F02}, - {0x2216, 0x9400}, {0x2215, 0x0E02}, {0x2215, 0x0F02}, {0x2215, 0x0F03}, - {0x2216, 0x7408}, {0x2215, 0x0E03}, {0x2215, 0x0F03}, {0x2215, 0x0F04}, - {0x2216, 0x4008}, {0x2215, 0x0E04}, {0x2215, 0x0F04}, {0x2215, 0x0F05}, - {0x2216, 0x9400}, {0x2215, 0x0E05}, {0x2215, 0x0F05}, {0x2215, 0x0F06}, - {0x2216, 0x0803}, {0x2215, 0x0E06}, {0x2215, 0x0F06}, {0x2215, 0x0D00}, - {0x2215, 0x0100}, {0x221F, 0x0001}, {0x2210, 0xF05E}, {0x221F, 0x0000}, - {0x2217, 0x2100}, {0x221F, 0x0000}, {0x220D, 0x0003}, {0x220E, 0x0015}, - {0x220D, 0x4003}, {0x220E, 0x0006}, {0x221F, 0x0000}, {0x2200, 0x1340}, - {0x133F, 0x0010}, {0x12A0, 0x0058}, {0x12A1, 0x0058}, {0x133E, 0x000E}, - {0x133F, 0x0030}, {0x221F, 0x0000}, {0x2210, 0x0166}, {0x221F, 0x0000}, - {0x133E, 0x000E}, {0x133F, 0x0010}, {0x133F, 0x0030}, {0x133E, 0x000E}, - {0x221F, 0x0005}, {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8B6E}, - {0x2206, 0x0000}, {0x220F, 0x0100}, {0x2205, 0x8000}, {0x2206, 0x0280}, - {0x2206, 0x28F7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, - {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, - {0x2206, 0x6602}, {0x2206, 0x80B9}, {0x2206, 0xE08B}, {0x2206, 0x8CE1}, - {0x2206, 0x8B8D}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x8E1E}, - {0x2206, 0x01A0}, {0x2206, 0x00E7}, {0x2206, 0xAEDB}, {0x2206, 0xEEE0}, - {0x2206, 0x120E}, {0x2206, 0xEEE0}, {0x2206, 0x1300}, {0x2206, 0xEEE0}, - {0x2206, 0x2001}, {0x2206, 0xEEE0}, {0x2206, 0x2166}, {0x2206, 0xEEE0}, - {0x2206, 0xC463}, {0x2206, 0xEEE0}, {0x2206, 0xC5E8}, {0x2206, 0xEEE0}, - {0x2206, 0xC699}, {0x2206, 0xEEE0}, {0x2206, 0xC7C2}, {0x2206, 0xEEE0}, - {0x2206, 0xC801}, {0x2206, 0xEEE0}, {0x2206, 0xC913}, {0x2206, 0xEEE0}, - {0x2206, 0xCA30}, {0x2206, 0xEEE0}, {0x2206, 0xCB3E}, {0x2206, 0xEEE0}, - {0x2206, 0xDCE1}, {0x2206, 0xEEE0}, {0x2206, 0xDD00}, {0x2206, 0xEEE2}, - {0x2206, 0x0001}, {0x2206, 0xEEE2}, {0x2206, 0x0100}, {0x2206, 0xEEE4}, - {0x2206, 0x8860}, {0x2206, 0xEEE4}, {0x2206, 0x8902}, {0x2206, 0xEEE4}, - {0x2206, 0x8C00}, {0x2206, 0xEEE4}, {0x2206, 0x8D30}, {0x2206, 0xEEEA}, - {0x2206, 0x1480}, {0x2206, 0xEEEA}, {0x2206, 0x1503}, {0x2206, 0xEEEA}, - {0x2206, 0xC600}, {0x2206, 0xEEEA}, {0x2206, 0xC706}, {0x2206, 0xEE85}, - {0x2206, 0xEE00}, {0x2206, 0xEE85}, {0x2206, 0xEF00}, {0x2206, 0xEE8B}, - {0x2206, 0x6750}, {0x2206, 0xEE8B}, {0x2206, 0x6632}, {0x2206, 0xEE8A}, - {0x2206, 0xD448}, {0x2206, 0xEE8A}, {0x2206, 0xD548}, {0x2206, 0xEE8A}, - {0x2206, 0xD649}, {0x2206, 0xEE8A}, {0x2206, 0xD7F8}, {0x2206, 0xEE8B}, - {0x2206, 0x85E2}, {0x2206, 0xEE8B}, {0x2206, 0x8700}, {0x2206, 0xEEFF}, - {0x2206, 0xF600}, {0x2206, 0xEEFF}, {0x2206, 0xF7FC}, {0x2206, 0x04F8}, - {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2023}, {0x2206, 0xF620}, - {0x2206, 0xE48B}, {0x2206, 0x8E02}, {0x2206, 0x2877}, {0x2206, 0x0225}, - {0x2206, 0xC702}, {0x2206, 0x26A1}, {0x2206, 0x0281}, {0x2206, 0xB302}, - {0x2206, 0x8496}, {0x2206, 0x0202}, {0x2206, 0xA102}, {0x2206, 0x27F1}, - {0x2206, 0x0228}, {0x2206, 0xF902}, {0x2206, 0x2AA0}, {0x2206, 0x0282}, - {0x2206, 0xB8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD21}, {0x2206, 0x08F6}, - {0x2206, 0x21E4}, {0x2206, 0x8B8E}, {0x2206, 0x0202}, {0x2206, 0x80E0}, - {0x2206, 0x8B8E}, {0x2206, 0xAD22}, {0x2206, 0x05F6}, {0x2206, 0x22E4}, - {0x2206, 0x8B8E}, {0x2206, 0xE08B}, {0x2206, 0x8EAD}, {0x2206, 0x2305}, - {0x2206, 0xF623}, {0x2206, 0xE48B}, {0x2206, 0x8EE0}, {0x2206, 0x8B8E}, - {0x2206, 0xAD24}, {0x2206, 0x08F6}, {0x2206, 0x24E4}, {0x2206, 0x8B8E}, - {0x2206, 0x0227}, {0x2206, 0x6AE0}, {0x2206, 0x8B8E}, {0x2206, 0xAD25}, - {0x2206, 0x05F6}, {0x2206, 0x25E4}, {0x2206, 0x8B8E}, {0x2206, 0xE08B}, - {0x2206, 0x8EAD}, {0x2206, 0x260B}, {0x2206, 0xF626}, {0x2206, 0xE48B}, - {0x2206, 0x8E02}, {0x2206, 0x830D}, {0x2206, 0x021D}, {0x2206, 0x6BE0}, - {0x2206, 0x8B8E}, {0x2206, 0xAD27}, {0x2206, 0x05F6}, {0x2206, 0x27E4}, - {0x2206, 0x8B8E}, {0x2206, 0x0281}, {0x2206, 0x4402}, {0x2206, 0x045C}, - {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B83}, {0x2206, 0xAD23}, - {0x2206, 0x30E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x2359}, - {0x2206, 0x02E0}, {0x2206, 0x85EF}, {0x2206, 0xE585}, {0x2206, 0xEFAC}, - {0x2206, 0x2907}, {0x2206, 0x1F01}, {0x2206, 0x9E51}, {0x2206, 0xAD29}, - {0x2206, 0x20E0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x06E1}, - {0x2206, 0x8B84}, {0x2206, 0xAD28}, {0x2206, 0x42E0}, {0x2206, 0x8B85}, - {0x2206, 0xAD21}, {0x2206, 0x06E1}, {0x2206, 0x8B84}, {0x2206, 0xAD29}, - {0x2206, 0x36BF}, {0x2206, 0x34BF}, {0x2206, 0x022C}, {0x2206, 0x31AE}, - {0x2206, 0x2EE0}, {0x2206, 0x8B83}, {0x2206, 0xAD21}, {0x2206, 0x10E0}, - {0x2206, 0x8B84}, {0x2206, 0xF620}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, - {0x2206, 0x8ADA}, {0x2206, 0x00EE}, {0x2206, 0x8ADB}, {0x2206, 0x00E0}, - {0x2206, 0x8B85}, {0x2206, 0xAD21}, {0x2206, 0x0CE0}, {0x2206, 0x8B84}, - {0x2206, 0xF621}, {0x2206, 0xE48B}, {0x2206, 0x84EE}, {0x2206, 0x8B72}, - {0x2206, 0xFFBF}, {0x2206, 0x34C2}, {0x2206, 0x022C}, {0x2206, 0x31FC}, - {0x2206, 0x04F8}, {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B85}, - {0x2206, 0xAD21}, {0x2206, 0x42E0}, {0x2206, 0xE022}, {0x2206, 0xE1E0}, - {0x2206, 0x2358}, {0x2206, 0xC059}, {0x2206, 0x021E}, {0x2206, 0x01E1}, - {0x2206, 0x8B72}, {0x2206, 0x1F10}, {0x2206, 0x9E2F}, {0x2206, 0xE48B}, - {0x2206, 0x72AD}, {0x2206, 0x2123}, {0x2206, 0xE18B}, {0x2206, 0x84F7}, - {0x2206, 0x29E5}, {0x2206, 0x8B84}, {0x2206, 0xAC27}, {0x2206, 0x10AC}, - {0x2206, 0x2605}, {0x2206, 0x0205}, {0x2206, 0x23AE}, {0x2206, 0x1602}, - {0x2206, 0x0535}, {0x2206, 0x0282}, {0x2206, 0x30AE}, {0x2206, 0x0E02}, - {0x2206, 0x056A}, {0x2206, 0x0282}, {0x2206, 0x75AE}, {0x2206, 0x0602}, - {0x2206, 0x04DC}, {0x2206, 0x0282}, {0x2206, 0x04EF}, {0x2206, 0x96FE}, - {0x2206, 0xFC04}, {0x2206, 0xF8F9}, {0x2206, 0xE08B}, {0x2206, 0x87AD}, - {0x2206, 0x2321}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, - {0x2206, 0xAD26}, {0x2206, 0x18F6}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, - {0x2206, 0xE5EA}, {0x2206, 0x15F6}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, - {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, - {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, - {0x2206, 0xE08B}, {0x2206, 0x87AD}, {0x2206, 0x233A}, {0x2206, 0xAD22}, - {0x2206, 0x37E0}, {0x2206, 0xE020}, {0x2206, 0xE1E0}, {0x2206, 0x21AC}, - {0x2206, 0x212E}, {0x2206, 0xE0EA}, {0x2206, 0x14E1}, {0x2206, 0xEA15}, - {0x2206, 0xF627}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, - {0x2206, 0xE2EA}, {0x2206, 0x12E3}, {0x2206, 0xEA13}, {0x2206, 0x5A8F}, - {0x2206, 0x6A20}, {0x2206, 0xE6EA}, {0x2206, 0x12E7}, {0x2206, 0xEA13}, - {0x2206, 0xF726}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, - {0x2206, 0xF727}, {0x2206, 0xE4EA}, {0x2206, 0x14E5}, {0x2206, 0xEA15}, - {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B87}, - {0x2206, 0xAD23}, {0x2206, 0x38AD}, {0x2206, 0x2135}, {0x2206, 0xE0E0}, - {0x2206, 0x20E1}, {0x2206, 0xE021}, {0x2206, 0xAC21}, {0x2206, 0x2CE0}, - {0x2206, 0xEA14}, {0x2206, 0xE1EA}, {0x2206, 0x15F6}, {0x2206, 0x27E4}, - {0x2206, 0xEA14}, {0x2206, 0xE5EA}, {0x2206, 0x15E2}, {0x2206, 0xEA12}, - {0x2206, 0xE3EA}, {0x2206, 0x135A}, {0x2206, 0x8FE6}, {0x2206, 0xEA12}, - {0x2206, 0xE7EA}, {0x2206, 0x13F7}, {0x2206, 0x26E4}, {0x2206, 0xEA14}, - {0x2206, 0xE5EA}, {0x2206, 0x15F7}, {0x2206, 0x27E4}, {0x2206, 0xEA14}, - {0x2206, 0xE5EA}, {0x2206, 0x15FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, - {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2146}, - {0x2206, 0xE0E0}, {0x2206, 0x22E1}, {0x2206, 0xE023}, {0x2206, 0x58C0}, - {0x2206, 0x5902}, {0x2206, 0x1E01}, {0x2206, 0xE18B}, {0x2206, 0x651F}, - {0x2206, 0x109E}, {0x2206, 0x33E4}, {0x2206, 0x8B65}, {0x2206, 0xAD21}, - {0x2206, 0x22AD}, {0x2206, 0x272A}, {0x2206, 0xD400}, {0x2206, 0x01BF}, - {0x2206, 0x34F2}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, {0x2206, 0x34F5}, - {0x2206, 0x022C}, {0x2206, 0xE0E0}, {0x2206, 0x8B67}, {0x2206, 0x1B10}, - {0x2206, 0xAA14}, {0x2206, 0xE18B}, {0x2206, 0x660D}, {0x2206, 0x1459}, - {0x2206, 0x0FAE}, {0x2206, 0x05E1}, {0x2206, 0x8B66}, {0x2206, 0x590F}, - {0x2206, 0xBF85}, {0x2206, 0x6102}, {0x2206, 0x2CA2}, {0x2206, 0xEF96}, - {0x2206, 0xFEFC}, {0x2206, 0x04F8}, {0x2206, 0xF9FA}, {0x2206, 0xFBEF}, - {0x2206, 0x79E2}, {0x2206, 0x8AD2}, {0x2206, 0xAC19}, {0x2206, 0x2DE0}, - {0x2206, 0xE036}, {0x2206, 0xE1E0}, {0x2206, 0x37EF}, {0x2206, 0x311F}, - {0x2206, 0x325B}, {0x2206, 0x019E}, {0x2206, 0x1F7A}, {0x2206, 0x0159}, - {0x2206, 0x019F}, {0x2206, 0x0ABF}, {0x2206, 0x348E}, {0x2206, 0x022C}, - {0x2206, 0x31F6}, {0x2206, 0x06AE}, {0x2206, 0x0FF6}, {0x2206, 0x0302}, - {0x2206, 0x0470}, {0x2206, 0xF703}, {0x2206, 0xF706}, {0x2206, 0xBF34}, - {0x2206, 0x9302}, {0x2206, 0x2C31}, {0x2206, 0xAC1A}, {0x2206, 0x25E0}, - {0x2206, 0xE022}, {0x2206, 0xE1E0}, {0x2206, 0x23EF}, {0x2206, 0x300D}, - {0x2206, 0x311F}, {0x2206, 0x325B}, {0x2206, 0x029E}, {0x2206, 0x157A}, - {0x2206, 0x0258}, {0x2206, 0xC4A0}, {0x2206, 0x0408}, {0x2206, 0xBF34}, - {0x2206, 0x9E02}, {0x2206, 0x2C31}, {0x2206, 0xAE06}, {0x2206, 0xBF34}, - {0x2206, 0x9C02}, {0x2206, 0x2C31}, {0x2206, 0xAC1B}, {0x2206, 0x4AE0}, - {0x2206, 0xE012}, {0x2206, 0xE1E0}, {0x2206, 0x13EF}, {0x2206, 0x300D}, - {0x2206, 0x331F}, {0x2206, 0x325B}, {0x2206, 0x1C9E}, {0x2206, 0x3AEF}, - {0x2206, 0x325B}, {0x2206, 0x1C9F}, {0x2206, 0x09BF}, {0x2206, 0x3498}, - {0x2206, 0x022C}, {0x2206, 0x3102}, {0x2206, 0x83C5}, {0x2206, 0x5A03}, - {0x2206, 0x0D03}, {0x2206, 0x581C}, {0x2206, 0x1E20}, {0x2206, 0x0207}, - {0x2206, 0xA0A0}, {0x2206, 0x000E}, {0x2206, 0x0284}, {0x2206, 0x17AD}, - {0x2206, 0x1817}, {0x2206, 0xBF34}, {0x2206, 0x9A02}, {0x2206, 0x2C31}, - {0x2206, 0xAE0F}, {0x2206, 0xBF34}, {0x2206, 0xC802}, {0x2206, 0x2C31}, - {0x2206, 0xBF34}, {0x2206, 0xC502}, {0x2206, 0x2C31}, {0x2206, 0x0284}, - {0x2206, 0x52E6}, {0x2206, 0x8AD2}, {0x2206, 0xEF97}, {0x2206, 0xFFFE}, - {0x2206, 0xFDFC}, {0x2206, 0x04F8}, {0x2206, 0xBF34}, {0x2206, 0xDA02}, - {0x2206, 0x2CE0}, {0x2206, 0xE58A}, {0x2206, 0xD3BF}, {0x2206, 0x34D4}, - {0x2206, 0x022C}, {0x2206, 0xE00C}, {0x2206, 0x1159}, {0x2206, 0x02E0}, - {0x2206, 0x8AD3}, {0x2206, 0x1E01}, {0x2206, 0xE48A}, {0x2206, 0xD3D1}, - {0x2206, 0x00BF}, {0x2206, 0x34DA}, {0x2206, 0x022C}, {0x2206, 0xA2D1}, - {0x2206, 0x01BF}, {0x2206, 0x34D4}, {0x2206, 0x022C}, {0x2206, 0xA2BF}, - {0x2206, 0x34CB}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, {0x2206, 0x8ACE}, - {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CE0}, {0x2206, 0xE58A}, - {0x2206, 0xCFBF}, {0x2206, 0x8564}, {0x2206, 0x022C}, {0x2206, 0xE0E5}, - {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, {0x2206, 0x2CE0}, - {0x2206, 0xE58A}, {0x2206, 0xD1FC}, {0x2206, 0x04F8}, {0x2206, 0xE18A}, - {0x2206, 0xD1BF}, {0x2206, 0x856A}, {0x2206, 0x022C}, {0x2206, 0xA2E1}, - {0x2206, 0x8AD0}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, - {0x2206, 0xE18A}, {0x2206, 0xCFBF}, {0x2206, 0x8567}, {0x2206, 0x022C}, - {0x2206, 0xA2E1}, {0x2206, 0x8ACE}, {0x2206, 0xBF34}, {0x2206, 0xCB02}, - {0x2206, 0x2CA2}, {0x2206, 0xE18A}, {0x2206, 0xD3BF}, {0x2206, 0x34DA}, - {0x2206, 0x022C}, {0x2206, 0xA2E1}, {0x2206, 0x8AD3}, {0x2206, 0x0D11}, - {0x2206, 0xBF34}, {0x2206, 0xD402}, {0x2206, 0x2CA2}, {0x2206, 0xFC04}, - {0x2206, 0xF9A0}, {0x2206, 0x0405}, {0x2206, 0xE38A}, {0x2206, 0xD4AE}, - {0x2206, 0x13A0}, {0x2206, 0x0805}, {0x2206, 0xE38A}, {0x2206, 0xD5AE}, - {0x2206, 0x0BA0}, {0x2206, 0x0C05}, {0x2206, 0xE38A}, {0x2206, 0xD6AE}, - {0x2206, 0x03E3}, {0x2206, 0x8AD7}, {0x2206, 0xEF13}, {0x2206, 0xBF34}, - {0x2206, 0xCB02}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, {0x2206, 0x0D11}, - {0x2206, 0xBF85}, {0x2206, 0x6702}, {0x2206, 0x2CA2}, {0x2206, 0xEF13}, - {0x2206, 0x0D14}, {0x2206, 0xBF85}, {0x2206, 0x6402}, {0x2206, 0x2CA2}, - {0x2206, 0xEF13}, {0x2206, 0x0D17}, {0x2206, 0xBF85}, {0x2206, 0x6A02}, - {0x2206, 0x2CA2}, {0x2206, 0xFD04}, {0x2206, 0xF8E0}, {0x2206, 0x8B85}, - {0x2206, 0xAD27}, {0x2206, 0x2DE0}, {0x2206, 0xE036}, {0x2206, 0xE1E0}, - {0x2206, 0x37E1}, {0x2206, 0x8B73}, {0x2206, 0x1F10}, {0x2206, 0x9E20}, - {0x2206, 0xE48B}, {0x2206, 0x73AC}, {0x2206, 0x200B}, {0x2206, 0xAC21}, - {0x2206, 0x0DAC}, {0x2206, 0x250F}, {0x2206, 0xAC27}, {0x2206, 0x0EAE}, - {0x2206, 0x0F02}, {0x2206, 0x84CC}, {0x2206, 0xAE0A}, {0x2206, 0x0284}, - {0x2206, 0xD1AE}, {0x2206, 0x05AE}, {0x2206, 0x0302}, {0x2206, 0x84D8}, - {0x2206, 0xFC04}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0x0402}, - {0x2206, 0x84E5}, {0x2206, 0x0285}, {0x2206, 0x2804}, {0x2206, 0x0285}, - {0x2206, 0x4904}, {0x2206, 0xEE8B}, {0x2206, 0x6800}, {0x2206, 0xEE8B}, - {0x2206, 0x6902}, {0x2206, 0x04F8}, {0x2206, 0xF9E0}, {0x2206, 0x8B85}, - {0x2206, 0xAD26}, {0x2206, 0x38D0}, {0x2206, 0x0B02}, {0x2206, 0x2B4D}, - {0x2206, 0x5882}, {0x2206, 0x7882}, {0x2206, 0x9F2D}, {0x2206, 0xE08B}, - {0x2206, 0x68E1}, {0x2206, 0x8B69}, {0x2206, 0x1F10}, {0x2206, 0x9EC8}, - {0x2206, 0x10E4}, {0x2206, 0x8B68}, {0x2206, 0xE0E0}, {0x2206, 0x00E1}, - {0x2206, 0xE001}, {0x2206, 0xF727}, {0x2206, 0xE4E0}, {0x2206, 0x00E5}, - {0x2206, 0xE001}, {0x2206, 0xE2E0}, {0x2206, 0x20E3}, {0x2206, 0xE021}, - {0x2206, 0xAD30}, {0x2206, 0xF7F6}, {0x2206, 0x27E4}, {0x2206, 0xE000}, - {0x2206, 0xE5E0}, {0x2206, 0x01FD}, {0x2206, 0xFC04}, {0x2206, 0xF8FA}, - {0x2206, 0xEF69}, {0x2206, 0xE08B}, {0x2206, 0x86AD}, {0x2206, 0x2212}, - {0x2206, 0xE0E0}, {0x2206, 0x14E1}, {0x2206, 0xE015}, {0x2206, 0xAD26}, - {0x2206, 0x9CE1}, {0x2206, 0x85E0}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, - {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x04F8}, - {0x2206, 0xFAEF}, {0x2206, 0x69E0}, {0x2206, 0x8B86}, {0x2206, 0xAD22}, - {0x2206, 0x09E1}, {0x2206, 0x85E1}, {0x2206, 0xBF85}, {0x2206, 0x6D02}, - {0x2206, 0x2CA2}, {0x2206, 0xEF96}, {0x2206, 0xFEFC}, {0x2206, 0x0464}, - {0x2206, 0xE48C}, {0x2206, 0xFDE4}, {0x2206, 0x80CA}, {0x2206, 0xE480}, - {0x2206, 0x66E0}, {0x2206, 0x8E70}, {0x2206, 0xE076}, {0x2205, 0xE142}, - {0x2206, 0x0701}, {0x2205, 0xE140}, {0x2206, 0x0405}, {0x220F, 0x0000}, - {0x221F, 0x0000}, {0x2200, 0x1340}, {0x133E, 0x000E}, {0x133F, 0x0010}, - {0x13EB, 0x11BB} -}; - -static const struct rtl8367b_initval rtl8367r_vb_initvals_1[] = { - {0x1B03, 0x0876}, {0x1200, 0x7FC4}, {0x1305, 0xC000}, {0x121E, 0x03CA}, - {0x1233, 0x0352}, {0x1234, 0x0064}, {0x1237, 0x0096}, {0x1238, 0x0078}, - {0x1239, 0x0084}, {0x123A, 0x0030}, {0x205F, 0x0002}, {0x2059, 0x1A00}, - {0x205F, 0x0000}, {0x207F, 0x0002}, {0x2077, 0x0000}, {0x2078, 0x0000}, - {0x2079, 0x0000}, {0x207A, 0x0000}, {0x207B, 0x0000}, {0x207F, 0x0000}, - {0x205F, 0x0002}, {0x2053, 0x0000}, {0x2054, 0x0000}, {0x2055, 0x0000}, - {0x2056, 0x0000}, {0x2057, 0x0000}, {0x205F, 0x0000}, {0x133F, 0x0030}, - {0x133E, 0x000E}, {0x221F, 0x0005}, {0x2205, 0x8B86}, {0x2206, 0x800E}, - {0x221F, 0x0000}, {0x133F, 0x0010}, {0x12A3, 0x2200}, {0x6107, 0xE58B}, - {0x6103, 0xA970}, {0x0018, 0x0F00}, {0x0038, 0x0F00}, {0x0058, 0x0F00}, - {0x0078, 0x0F00}, {0x0098, 0x0F00}, {0x133F, 0x0030}, {0x133E, 0x000E}, - {0x221F, 0x0005}, {0x2205, 0x8B6E}, {0x2206, 0x0000}, {0x220F, 0x0100}, - {0x2205, 0xFFF6}, {0x2206, 0x0080}, {0x2205, 0x8000}, {0x2206, 0x0280}, - {0x2206, 0x2BF7}, {0x2206, 0x00E0}, {0x2206, 0xFFF7}, {0x2206, 0xA080}, - {0x2206, 0x02AE}, {0x2206, 0xF602}, {0x2206, 0x0153}, {0x2206, 0x0201}, - {0x2206, 0x6602}, {0x2206, 0x8044}, {0x2206, 0x0201}, {0x2206, 0x7CE0}, - {0x2206, 0x8B8C}, {0x2206, 0xE18B}, {0x2206, 0x8D1E}, {0x2206, 0x01E1}, - {0x2206, 0x8B8E}, {0x2206, 0x1E01}, {0x2206, 0xA000}, {0x2206, 0xE4AE}, - {0x2206, 0xD8EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, {0x2206, 0x85C1}, - {0x2206, 0x00EE}, {0x2206, 0x8AFC}, {0x2206, 0x07EE}, {0x2206, 0x8AFD}, - {0x2206, 0x73EE}, {0x2206, 0xFFF6}, {0x2206, 0x00EE}, {0x2206, 0xFFF7}, - {0x2206, 0xFC04}, {0x2206, 0xF8E0}, {0x2206, 0x8B8E}, {0x2206, 0xAD20}, - {0x2206, 0x0302}, {0x2206, 0x8050}, {0x2206, 0xFC04}, {0x2206, 0xF8F9}, - {0x2206, 0xE08B}, {0x2206, 0x85AD}, {0x2206, 0x2548}, {0x2206, 0xE08A}, - {0x2206, 0xE4E1}, {0x2206, 0x8AE5}, {0x2206, 0x7C00}, {0x2206, 0x009E}, - {0x2206, 0x35EE}, {0x2206, 0x8AE4}, {0x2206, 0x00EE}, {0x2206, 0x8AE5}, - {0x2206, 0x00E0}, {0x2206, 0x8AFC}, {0x2206, 0xE18A}, {0x2206, 0xFDE2}, - {0x2206, 0x85C0}, {0x2206, 0xE385}, {0x2206, 0xC102}, {0x2206, 0x2DAC}, - {0x2206, 0xAD20}, {0x2206, 0x12EE}, {0x2206, 0x8AE4}, {0x2206, 0x03EE}, - {0x2206, 0x8AE5}, {0x2206, 0xB7EE}, {0x2206, 0x85C0}, {0x2206, 0x00EE}, - {0x2206, 0x85C1}, {0x2206, 0x00AE}, {0x2206, 0x1115}, {0x2206, 0xE685}, - {0x2206, 0xC0E7}, {0x2206, 0x85C1}, {0x2206, 0xAE08}, {0x2206, 0xEE85}, - {0x2206, 0xC000}, {0x2206, 0xEE85}, {0x2206, 0xC100}, {0x2206, 0xFDFC}, - {0x2206, 0x0400}, {0x2205, 0xE142}, {0x2206, 0x0701}, {0x2205, 0xE140}, - {0x2206, 0x0405}, {0x220F, 0x0000}, {0x221F, 0x0000}, {0x133E, 0x000E}, - {0x133F, 0x0010}, {0x13EB, 0x11BB}, {0x207F, 0x0002}, {0x2073, 0x1D22}, - {0x207F, 0x0000}, {0x133F, 0x0030}, {0x133E, 0x000E}, {0x2200, 0x1340}, - {0x133E, 0x000E}, {0x133F, 0x0010}, -}; - -static int rtl8367b_write_initvals(struct rtl8366_smi *smi, - const struct rtl8367b_initval *initvals, - int count) -{ - int err; - int i; - - for (i = 0; i < count; i++) - REG_WR(smi, initvals[i].reg, initvals[i].val); - - return 0; -} - -static int rtl8367b_read_phy_reg(struct rtl8366_smi *smi, - u32 phy_addr, u32 phy_reg, u32 *val) -{ - int timeout; - u32 data; - int err; - - if (phy_addr > RTL8367B_PHY_ADDR_MAX) - return -EINVAL; - - if (phy_reg > RTL8367B_PHY_REG_MAX) - return -EINVAL; - - REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); - if (data & RTL8367B_IA_STATUS_PHY_BUSY) - return -ETIMEDOUT; - - /* prepare address */ - REG_WR(smi, RTL8367B_IA_ADDRESS_REG, - RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); - - /* send read command */ - REG_WR(smi, RTL8367B_IA_CTRL_REG, - RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_READ); - - timeout = 5; - do { - REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); - if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) - break; - - if (timeout--) { - dev_err(smi->parent, "phy read timed out\n"); - return -ETIMEDOUT; - } - - udelay(1); - } while (1); - - /* read data */ - REG_RD(smi, RTL8367B_IA_READ_DATA_REG, val); - - dev_dbg(smi->parent, "phy_read: addr:%02x, reg:%02x, val:%04x\n", - phy_addr, phy_reg, *val); - return 0; -} - -static int rtl8367b_write_phy_reg(struct rtl8366_smi *smi, - u32 phy_addr, u32 phy_reg, u32 val) -{ - int timeout; - u32 data; - int err; - - dev_dbg(smi->parent, "phy_write: addr:%02x, reg:%02x, val:%04x\n", - phy_addr, phy_reg, val); - - if (phy_addr > RTL8367B_PHY_ADDR_MAX) - return -EINVAL; - - if (phy_reg > RTL8367B_PHY_REG_MAX) - return -EINVAL; - - REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); - if (data & RTL8367B_IA_STATUS_PHY_BUSY) - return -ETIMEDOUT; - - /* preapre data */ - REG_WR(smi, RTL8367B_IA_WRITE_DATA_REG, val); - - /* prepare address */ - REG_WR(smi, RTL8367B_IA_ADDRESS_REG, - RTL8367B_INTERNAL_PHY_REG(phy_addr, phy_reg)); - - /* send write command */ - REG_WR(smi, RTL8367B_IA_CTRL_REG, - RTL8367B_IA_CTRL_CMD_MASK | RTL8367B_IA_CTRL_RW_WRITE); - - timeout = 5; - do { - REG_RD(smi, RTL8367B_IA_STATUS_REG, &data); - if ((data & RTL8367B_IA_STATUS_PHY_BUSY) == 0) - break; - - if (timeout--) { - dev_err(smi->parent, "phy write timed out\n"); - return -ETIMEDOUT; - } - - udelay(1); - } while (1); - - return 0; -} - -static int rtl8367b_init_regs(struct rtl8366_smi *smi) -{ - const struct rtl8367b_initval *initvals; - u32 chip_ver; - u32 rlvid; - int count; - int err; - - REG_WR(smi, RTL8367B_RTL_MAGIC_ID_REG, RTL8367B_RTL_MAGIC_ID_VAL); - REG_RD(smi, RTL8367B_CHIP_VER_REG, &chip_ver); - - rlvid = (chip_ver >> RTL8367B_CHIP_VER_RLVID_SHIFT) & - RTL8367B_CHIP_VER_RLVID_MASK; - - switch (rlvid) { - case 0: - initvals = rtl8367r_vb_initvals_0; - count = ARRAY_SIZE(rtl8367r_vb_initvals_0); - break; - - case 1: - initvals = rtl8367r_vb_initvals_1; - count = ARRAY_SIZE(rtl8367r_vb_initvals_1); - break; - - default: - dev_err(smi->parent, "unknow rlvid %u\n", rlvid); - return -ENODEV; - } - - /* TODO: disable RLTP */ - - return rtl8367b_write_initvals(smi, initvals, count); -} - -static int rtl8367b_reset_chip(struct rtl8366_smi *smi) -{ - int timeout = 10; - int err; - u32 data; - - REG_WR(smi, RTL8367B_CHIP_RESET_REG, RTL8367B_CHIP_RESET_HW); - msleep(RTL8367B_RESET_DELAY); - - do { - REG_RD(smi, RTL8367B_CHIP_RESET_REG, &data); - if (!(data & RTL8367B_CHIP_RESET_HW)) - break; - - msleep(1); - } while (--timeout); - - if (!timeout) { - dev_err(smi->parent, "chip reset timed out\n"); - return -ETIMEDOUT; - } - - return 0; -} - -static int rtl8367b_extif_set_mode(struct rtl8366_smi *smi, int id, - enum rtl8367_extif_mode mode) -{ - int err; - - /* set port mode */ - switch (mode) { - case RTL8367_EXTIF_MODE_RGMII: - case RTL8367_EXTIF_MODE_RGMII_33V: - REG_WR(smi, RTL8367B_CHIP_DEBUG0_REG, 0x0367); - REG_WR(smi, RTL8367B_CHIP_DEBUG1_REG, 0x7777); - break; - - case RTL8367_EXTIF_MODE_TMII_MAC: - case RTL8367_EXTIF_MODE_TMII_PHY: - REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, - BIT((id + 1) % 2), BIT((id + 1) % 2)); - break; - - case RTL8367_EXTIF_MODE_GMII: - REG_RMW(smi, RTL8367B_CHIP_DEBUG0_REG, - RTL8367B_CHIP_DEBUG0_DUMMY0(id), - RTL8367B_CHIP_DEBUG0_DUMMY0(id)); - REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), BIT(6)); - break; - - case RTL8367_EXTIF_MODE_MII_MAC: - case RTL8367_EXTIF_MODE_MII_PHY: - case RTL8367_EXTIF_MODE_DISABLED: - REG_RMW(smi, RTL8367B_BYPASS_LINE_RATE_REG, - BIT((id + 1) % 2), 0); - REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), BIT(6), 0); - break; - - default: - dev_err(smi->parent, - "invalid mode for external interface %d\n", id); - return -EINVAL; - } - - REG_RMW(smi, RTL8367B_DIS_REG, - RTL8367B_DIS_RGMII_MASK << RTL8367B_DIS_RGMII_SHIFT(id), - mode << RTL8367B_DIS_RGMII_SHIFT(id)); - - return 0; -} - -static int rtl8367b_extif_set_force(struct rtl8366_smi *smi, int id, - struct rtl8367_port_ability *pa) -{ - u32 mask; - u32 val; - int err; - - mask = (RTL8367B_DI_FORCE_MODE | - RTL8367B_DI_FORCE_NWAY | - RTL8367B_DI_FORCE_TXPAUSE | - RTL8367B_DI_FORCE_RXPAUSE | - RTL8367B_DI_FORCE_LINK | - RTL8367B_DI_FORCE_DUPLEX | - RTL8367B_DI_FORCE_SPEED_MASK); - - val = pa->speed; - val |= pa->force_mode ? RTL8367B_DI_FORCE_MODE : 0; - val |= pa->nway ? RTL8367B_DI_FORCE_NWAY : 0; - val |= pa->txpause ? RTL8367B_DI_FORCE_TXPAUSE : 0; - val |= pa->rxpause ? RTL8367B_DI_FORCE_RXPAUSE : 0; - val |= pa->link ? RTL8367B_DI_FORCE_LINK : 0; - val |= pa->duplex ? RTL8367B_DI_FORCE_DUPLEX : 0; - - REG_RMW(smi, RTL8367B_DI_FORCE_REG(id), mask, val); - - return 0; -} - -static int rtl8367b_extif_set_rgmii_delay(struct rtl8366_smi *smi, int id, - unsigned txdelay, unsigned rxdelay) -{ - u32 mask; - u32 val; - int err; - - mask = (RTL8367B_EXT_RGMXF_RXDELAY_MASK | - (RTL8367B_EXT_RGMXF_TXDELAY_MASK << - RTL8367B_EXT_RGMXF_TXDELAY_SHIFT)); - - val = rxdelay; - val |= txdelay << RTL8367B_EXT_RGMXF_TXDELAY_SHIFT; - - REG_RMW(smi, RTL8367B_EXT_RGMXF_REG(id), mask, val); - - return 0; -} - -static int rtl8367b_extif_init(struct rtl8366_smi *smi, int id, - struct rtl8367_extif_config *cfg) -{ - enum rtl8367_extif_mode mode; - int err; - - mode = (cfg) ? cfg->mode : RTL8367_EXTIF_MODE_DISABLED; - - err = rtl8367b_extif_set_mode(smi, id, mode); - if (err) - return err; - - if (mode != RTL8367_EXTIF_MODE_DISABLED) { - err = rtl8367b_extif_set_force(smi, id, &cfg->ability); - if (err) - return err; - - err = rtl8367b_extif_set_rgmii_delay(smi, id, cfg->txdelay, - cfg->rxdelay); - if (err) - return err; - } - - return 0; -} - -#ifdef CONFIG_OF -static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, - const char *name) -{ - struct rtl8367_extif_config *cfg; - const __be32 *prop; - int size; - int err; - - prop = of_get_property(smi->parent->of_node, name, &size); - if (!prop) - return rtl8367b_extif_init(smi, id, NULL); - - if (size != (9 * sizeof(*prop))) { - dev_err(smi->parent, "%s property is invalid\n", name); - return -EINVAL; - } - - cfg = kzalloc(sizeof(struct rtl8367_extif_config), GFP_KERNEL); - if (!cfg) - return -ENOMEM; - - cfg->txdelay = be32_to_cpup(prop++); - cfg->rxdelay = be32_to_cpup(prop++); - cfg->mode = be32_to_cpup(prop++); - cfg->ability.force_mode = be32_to_cpup(prop++); - cfg->ability.txpause = be32_to_cpup(prop++); - cfg->ability.rxpause = be32_to_cpup(prop++); - cfg->ability.link = be32_to_cpup(prop++); - cfg->ability.duplex = be32_to_cpup(prop++); - cfg->ability.speed = be32_to_cpup(prop++); - - err = rtl8367b_extif_init(smi, id, cfg); - kfree(cfg); - - return err; -} -#else -static int rtl8367b_extif_init_of(struct rtl8366_smi *smi, int id, - const char *name) -{ - return -EINVAL; -} -#endif - -static int rtl8367b_setup(struct rtl8366_smi *smi) -{ - struct rtl8367_platform_data *pdata; - int err; - int i; - - pdata = smi->parent->platform_data; - - err = rtl8367b_init_regs(smi); - if (err) - return err; - - /* initialize external interfaces */ - if (smi->parent->of_node) { - err = rtl8367b_extif_init_of(smi, 0, "realtek,extif0"); - if (err) - return err; - - err = rtl8367b_extif_init_of(smi, 1, "realtek,extif1"); - if (err) - return err; - } else { - err = rtl8367b_extif_init(smi, 0, pdata->extif0_cfg); - if (err) - return err; - - err = rtl8367b_extif_init(smi, 1, pdata->extif1_cfg); - if (err) - return err; - } - - /* set maximum packet length to 1536 bytes */ - REG_RMW(smi, RTL8367B_SWC0_REG, RTL8367B_SWC0_MAX_LENGTH_MASK, - RTL8367B_SWC0_MAX_LENGTH_1536); - - /* - * discard VLAN tagged packets if the port is not a member of - * the VLAN with which the packets is associated. - */ - REG_WR(smi, RTL8367B_VLAN_INGRESS_REG, RTL8367B_PORTS_ALL); - - /* - * Setup egress tag mode for each port. - */ - for (i = 0; i < RTL8367B_NUM_PORTS; i++) - REG_RMW(smi, - RTL8367B_PORT_MISC_CFG_REG(i), - RTL8367B_PORT_MISC_CFG_EGRESS_MODE_MASK << - RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT, - RTL8367B_PORT_MISC_CFG_EGRESS_MODE_ORIGINAL << - RTL8367B_PORT_MISC_CFG_EGRESS_MODE_SHIFT); - - return 0; -} - -static int rtl8367b_get_mib_counter(struct rtl8366_smi *smi, int counter, - int port, unsigned long long *val) -{ - struct rtl8366_mib_counter *mib; - int offset; - int i; - int err; - u32 addr, data; - u64 mibvalue; - - if (port > RTL8367B_NUM_PORTS || - counter >= RTL8367B_NUM_MIB_COUNTERS) - return -EINVAL; - - mib = &rtl8367b_mib_counters[counter]; - addr = RTL8367B_MIB_COUNTER_PORT_OFFSET * port + mib->offset; - - /* - * Writing access counter address first - * then ASIC will prepare 64bits counter wait for being retrived - */ - REG_WR(smi, RTL8367B_MIB_ADDRESS_REG, addr >> 2); - - /* read MIB control register */ - REG_RD(smi, RTL8367B_MIB_CTRL0_REG(0), &data); - - if (data & RTL8367B_MIB_CTRL0_BUSY_MASK) - return -EBUSY; - - if (data & RTL8367B_MIB_CTRL0_RESET_MASK) - return -EIO; - - if (mib->length == 4) - offset = 3; - else - offset = (mib->offset + 1) % 4; - - mibvalue = 0; - for (i = 0; i < mib->length; i++) { - REG_RD(smi, RTL8367B_MIB_COUNTER_REG(offset - i), &data); - mibvalue = (mibvalue << 16) | (data & 0xFFFF); - } - - *val = mibvalue; - return 0; -} - -static int rtl8367b_get_vlan_4k(struct rtl8366_smi *smi, u32 vid, - struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; - int err; - int i; - - memset(vlan4k, '\0', sizeof(struct rtl8366_vlan_4k)); - - if (vid >= RTL8367B_NUM_VIDS) - return -EINVAL; - - /* write VID */ - REG_WR(smi, RTL8367B_TA_ADDR_REG, vid); - - /* write table access control word */ - REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_READ); - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_RD(smi, RTL8367B_TA_RDDATA_REG(i), &data[i]); - - vlan4k->vid = vid; - vlan4k->member = (data[0] >> RTL8367B_TA_VLAN0_MEMBER_SHIFT) & - RTL8367B_TA_VLAN0_MEMBER_MASK; - vlan4k->untag = (data[0] >> RTL8367B_TA_VLAN0_UNTAG_SHIFT) & - RTL8367B_TA_VLAN0_UNTAG_MASK; - vlan4k->fid = (data[1] >> RTL8367B_TA_VLAN1_FID_SHIFT) & - RTL8367B_TA_VLAN1_FID_MASK; - - return 0; -} - -static int rtl8367b_set_vlan_4k(struct rtl8366_smi *smi, - const struct rtl8366_vlan_4k *vlan4k) -{ - u32 data[RTL8367B_TA_VLAN_NUM_WORDS]; - int err; - int i; - - if (vlan4k->vid >= RTL8367B_NUM_VIDS || - vlan4k->member > RTL8367B_TA_VLAN0_MEMBER_MASK || - vlan4k->untag > RTL8367B_UNTAG_MASK || - vlan4k->fid > RTL8367B_FIDMAX) - return -EINVAL; - - memset(data, 0, sizeof(data)); - - data[0] = (vlan4k->member & RTL8367B_TA_VLAN0_MEMBER_MASK) << - RTL8367B_TA_VLAN0_MEMBER_SHIFT; - data[0] |= (vlan4k->untag & RTL8367B_TA_VLAN0_UNTAG_MASK) << - RTL8367B_TA_VLAN0_UNTAG_SHIFT; - data[1] = (vlan4k->fid & RTL8367B_TA_VLAN1_FID_MASK) << - RTL8367B_TA_VLAN1_FID_SHIFT; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_WR(smi, RTL8367B_TA_WRDATA_REG(i), data[i]); - - /* write VID */ - REG_WR(smi, RTL8367B_TA_ADDR_REG, - vlan4k->vid & RTL8367B_TA_VLAN_VID_MASK); - - /* write table access control word */ - REG_WR(smi, RTL8367B_TA_CTRL_REG, RTL8367B_TA_CTRL_CVLAN_WRITE); - - return 0; -} - -static int rtl8367b_get_vlan_mc(struct rtl8366_smi *smi, u32 index, - struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; - int err; - int i; - - memset(vlanmc, '\0', sizeof(struct rtl8366_vlan_mc)); - - if (index >= RTL8367B_NUM_VLANS) - return -EINVAL; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_RD(smi, RTL8367B_VLAN_MC_BASE(index) + i, &data[i]); - - vlanmc->member = (data[0] >> RTL8367B_VLAN_MC0_MEMBER_SHIFT) & - RTL8367B_VLAN_MC0_MEMBER_MASK; - vlanmc->fid = (data[1] >> RTL8367B_VLAN_MC1_FID_SHIFT) & - RTL8367B_VLAN_MC1_FID_MASK; - vlanmc->vid = (data[3] >> RTL8367B_VLAN_MC3_EVID_SHIFT) & - RTL8367B_VLAN_MC3_EVID_MASK; - - return 0; -} - -static int rtl8367b_set_vlan_mc(struct rtl8366_smi *smi, u32 index, - const struct rtl8366_vlan_mc *vlanmc) -{ - u32 data[RTL8367B_VLAN_MC_NUM_WORDS]; - int err; - int i; - - if (index >= RTL8367B_NUM_VLANS || - vlanmc->vid >= RTL8367B_NUM_VIDS || - vlanmc->priority > RTL8367B_PRIORITYMAX || - vlanmc->member > RTL8367B_VLAN_MC0_MEMBER_MASK || - vlanmc->untag > RTL8367B_UNTAG_MASK || - vlanmc->fid > RTL8367B_FIDMAX) - return -EINVAL; - - data[0] = (vlanmc->member & RTL8367B_VLAN_MC0_MEMBER_MASK) << - RTL8367B_VLAN_MC0_MEMBER_SHIFT; - data[1] = (vlanmc->fid & RTL8367B_VLAN_MC1_FID_MASK) << - RTL8367B_VLAN_MC1_FID_SHIFT; - data[2] = 0; - data[3] = (vlanmc->vid & RTL8367B_VLAN_MC3_EVID_MASK) << - RTL8367B_VLAN_MC3_EVID_SHIFT; - - for (i = 0; i < ARRAY_SIZE(data); i++) - REG_WR(smi, RTL8367B_VLAN_MC_BASE(index) + i, data[i]); - - return 0; -} - -static int rtl8367b_get_mc_index(struct rtl8366_smi *smi, int port, int *val) -{ - u32 data; - int err; - - if (port >= RTL8367B_NUM_PORTS) - return -EINVAL; - - REG_RD(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), &data); - - *val = (data >> RTL8367B_VLAN_PVID_CTRL_SHIFT(port)) & - RTL8367B_VLAN_PVID_CTRL_MASK; - - return 0; -} - -static int rtl8367b_set_mc_index(struct rtl8366_smi *smi, int port, int index) -{ - if (port >= RTL8367B_NUM_PORTS || index >= RTL8367B_NUM_VLANS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_PVID_CTRL_REG(port), - RTL8367B_VLAN_PVID_CTRL_MASK << - RTL8367B_VLAN_PVID_CTRL_SHIFT(port), - (index & RTL8367B_VLAN_PVID_CTRL_MASK) << - RTL8367B_VLAN_PVID_CTRL_SHIFT(port)); -} - -static int rtl8367b_enable_vlan(struct rtl8366_smi *smi, int enable) -{ - return rtl8366_smi_rmwr(smi, RTL8367B_VLAN_CTRL_REG, - RTL8367B_VLAN_CTRL_ENABLE, - (enable) ? RTL8367B_VLAN_CTRL_ENABLE : 0); -} - -static int rtl8367b_enable_vlan4k(struct rtl8366_smi *smi, int enable) -{ - return 0; -} - -static int rtl8367b_is_vlan_valid(struct rtl8366_smi *smi, unsigned vlan) -{ - unsigned max = RTL8367B_NUM_VLANS; - - if (smi->vlan4k_enabled) - max = RTL8367B_NUM_VIDS - 1; - - if (vlan == 0 || vlan >= max) - return 0; - - return 1; -} - -static int rtl8367b_enable_port(struct rtl8366_smi *smi, int port, int enable) -{ - int err; - - REG_WR(smi, RTL8367B_PORT_ISOLATION_REG(port), - (enable) ? RTL8367B_PORTS_ALL : 0); - - return 0; -} - -static int rtl8367b_sw_reset_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - - return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(0), 0, - RTL8367B_MIB_CTRL0_GLOBAL_RESET_MASK); -} - -static int rtl8367b_sw_get_port_link(struct switch_dev *dev, - int port, - struct switch_port_link *link) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data = 0; - u32 speed; - - if (port >= RTL8367B_NUM_PORTS) - return -EINVAL; - - rtl8366_smi_read_reg(smi, RTL8367B_PORT_STATUS_REG(port), &data); - - link->link = !!(data & RTL8367B_PORT_STATUS_LINK); - if (!link->link) - return 0; - - link->duplex = !!(data & RTL8367B_PORT_STATUS_DUPLEX); - link->rx_flow = !!(data & RTL8367B_PORT_STATUS_RXPAUSE); - link->tx_flow = !!(data & RTL8367B_PORT_STATUS_TXPAUSE); - link->aneg = !!(data & RTL8367B_PORT_STATUS_NWAY); - - speed = (data & RTL8367B_PORT_STATUS_SPEED_MASK); - switch (speed) { - case 0: - link->speed = SWITCH_PORT_SPEED_10; - break; - case 1: - link->speed = SWITCH_PORT_SPEED_100; - break; - case 2: - link->speed = SWITCH_PORT_SPEED_1000; - break; - default: - link->speed = SWITCH_PORT_SPEED_UNKNOWN; - break; - } - - return 0; -} - -static int rtl8367b_sw_get_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 data; - - rtl8366_smi_read_reg(smi, RTL8367B_SWC0_REG, &data); - val->value.i = (data & RTL8367B_SWC0_MAX_LENGTH_MASK) >> - RTL8367B_SWC0_MAX_LENGTH_SHIFT; - - return 0; -} - -static int rtl8367b_sw_set_max_length(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - u32 max_len; - - switch (val->value.i) { - case 0: - max_len = RTL8367B_SWC0_MAX_LENGTH_1522; - break; - case 1: - max_len = RTL8367B_SWC0_MAX_LENGTH_1536; - break; - case 2: - max_len = RTL8367B_SWC0_MAX_LENGTH_1552; - break; - case 3: - max_len = RTL8367B_SWC0_MAX_LENGTH_16000; - break; - default: - return -EINVAL; - } - - return rtl8366_smi_rmwr(smi, RTL8367B_SWC0_REG, - RTL8367B_SWC0_MAX_LENGTH_MASK, max_len); -} - - -static int rtl8367b_sw_reset_port_mibs(struct switch_dev *dev, - const struct switch_attr *attr, - struct switch_val *val) -{ - struct rtl8366_smi *smi = sw_to_rtl8366_smi(dev); - int port; - - port = val->port_vlan; - if (port >= RTL8367B_NUM_PORTS) - return -EINVAL; - - return rtl8366_smi_rmwr(smi, RTL8367B_MIB_CTRL0_REG(port / 8), 0, - RTL8367B_MIB_CTRL0_PORT_RESET_MASK(port % 8)); -} - -static int rtl8367b_sw_get_port_stats(struct switch_dev *dev, int port, - struct switch_port_stats *stats) -{ - return (rtl8366_sw_get_port_stats(dev, port, stats, - RTL8367B_MIB_TXB_ID, RTL8367B_MIB_RXB_ID)); -} - -static struct switch_attr rtl8367b_globals[] = { - { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan", - .description = "Enable VLAN mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 1 - }, { - .type = SWITCH_TYPE_INT, - .name = "enable_vlan4k", - .description = "Enable VLAN 4K mode", - .set = rtl8366_sw_set_vlan_enable, - .get = rtl8366_sw_get_vlan_enable, - .max = 1, - .ofs = 2 - }, { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mibs", - .description = "Reset all MIB counters", - .set = rtl8367b_sw_reset_mibs, - }, { - .type = SWITCH_TYPE_INT, - .name = "max_length", - .description = "Get/Set the maximum length of valid packets" - "(0:1522, 1:1536, 2:1552, 3:16000)", - .set = rtl8367b_sw_set_max_length, - .get = rtl8367b_sw_get_max_length, - .max = 3, - } -}; - -static struct switch_attr rtl8367b_port[] = { - { - .type = SWITCH_TYPE_NOVAL, - .name = "reset_mib", - .description = "Reset single port MIB counters", - .set = rtl8367b_sw_reset_port_mibs, - }, { - .type = SWITCH_TYPE_STRING, - .name = "mib", - .description = "Get MIB counters for port", - .max = 33, - .set = NULL, - .get = rtl8366_sw_get_port_mib, - }, -}; - -static struct switch_attr rtl8367b_vlan[] = { - { - .type = SWITCH_TYPE_STRING, - .name = "info", - .description = "Get vlan information", - .max = 1, - .set = NULL, - .get = rtl8366_sw_get_vlan_info, - }, -}; - -static const struct switch_dev_ops rtl8367b_sw_ops = { - .attr_global = { - .attr = rtl8367b_globals, - .n_attr = ARRAY_SIZE(rtl8367b_globals), - }, - .attr_port = { - .attr = rtl8367b_port, - .n_attr = ARRAY_SIZE(rtl8367b_port), - }, - .attr_vlan = { - .attr = rtl8367b_vlan, - .n_attr = ARRAY_SIZE(rtl8367b_vlan), - }, - - .get_vlan_ports = rtl8366_sw_get_vlan_ports, - .set_vlan_ports = rtl8366_sw_set_vlan_ports, - .get_port_pvid = rtl8366_sw_get_port_pvid, - .set_port_pvid = rtl8366_sw_set_port_pvid, - .reset_switch = rtl8366_sw_reset_switch, - .get_port_link = rtl8367b_sw_get_port_link, - .get_port_stats = rtl8367b_sw_get_port_stats, -}; - -static int rtl8367b_switch_init(struct rtl8366_smi *smi) -{ - struct switch_dev *dev = &smi->sw_dev; - int err; - - dev->name = "RTL8367B"; - dev->cpu_port = RTL8367B_CPU_PORT_NUM; - dev->ports = RTL8367B_NUM_PORTS; - dev->vlans = RTL8367B_NUM_VIDS; - dev->ops = &rtl8367b_sw_ops; - dev->alias = dev_name(smi->parent); - - err = register_switch(dev, NULL); - if (err) - dev_err(smi->parent, "switch registration failed\n"); - - return err; -} - -static void rtl8367b_switch_cleanup(struct rtl8366_smi *smi) -{ - unregister_switch(&smi->sw_dev); -} - -static int rtl8367b_mii_read(struct mii_bus *bus, int addr, int reg) -{ - struct rtl8366_smi *smi = bus->priv; - u32 val = 0; - int err; - - err = rtl8367b_read_phy_reg(smi, addr, reg, &val); - if (err) - return 0xffff; - - return val; -} - -static int rtl8367b_mii_write(struct mii_bus *bus, int addr, int reg, u16 val) -{ - struct rtl8366_smi *smi = bus->priv; - u32 t; - int err; - - err = rtl8367b_write_phy_reg(smi, addr, reg, val); - if (err) - return err; - - /* flush write */ - (void) rtl8367b_read_phy_reg(smi, addr, reg, &t); - - return err; -} - -static int rtl8367b_detect(struct rtl8366_smi *smi) -{ - const char *chip_name; - u32 chip_num; - u32 chip_ver; - u32 chip_mode; - int ret; - - /* TODO: improve chip detection */ - rtl8366_smi_write_reg(smi, RTL8367B_RTL_MAGIC_ID_REG, - RTL8367B_RTL_MAGIC_ID_VAL); - - ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_NUMBER_REG, &chip_num); - if (ret) { - dev_err(smi->parent, "unable to read %s register\n", - "chip number"); - return ret; - } - - ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_VER_REG, &chip_ver); - if (ret) { - dev_err(smi->parent, "unable to read %s register\n", - "chip version"); - return ret; - } - - ret = rtl8366_smi_read_reg(smi, RTL8367B_CHIP_MODE_REG, &chip_mode); - if (ret) { - dev_err(smi->parent, "unable to read %s register\n", - "chip mode"); - return ret; - } - - switch (chip_ver) { - case 0x1000: - chip_name = "8367RB"; - break; - case 0x1010: - chip_name = "8367R-VB"; - break; - default: - dev_err(smi->parent, - "unknown chip num:%04x ver:%04x, mode:%04x\n", - chip_num, chip_ver, chip_mode); - return -ENODEV; - } - - dev_info(smi->parent, "RTL%s chip found\n", chip_name); - - return 0; -} - -static struct rtl8366_smi_ops rtl8367b_smi_ops = { - .detect = rtl8367b_detect, - .reset_chip = rtl8367b_reset_chip, - .setup = rtl8367b_setup, - - .mii_read = rtl8367b_mii_read, - .mii_write = rtl8367b_mii_write, - - .get_vlan_mc = rtl8367b_get_vlan_mc, - .set_vlan_mc = rtl8367b_set_vlan_mc, - .get_vlan_4k = rtl8367b_get_vlan_4k, - .set_vlan_4k = rtl8367b_set_vlan_4k, - .get_mc_index = rtl8367b_get_mc_index, - .set_mc_index = rtl8367b_set_mc_index, - .get_mib_counter = rtl8367b_get_mib_counter, - .is_vlan_valid = rtl8367b_is_vlan_valid, - .enable_vlan = rtl8367b_enable_vlan, - .enable_vlan4k = rtl8367b_enable_vlan4k, - .enable_port = rtl8367b_enable_port, -}; - -static int rtl8367b_probe(struct platform_device *pdev) -{ - struct rtl8366_smi *smi; - int err; - - smi = rtl8366_smi_probe(pdev); - if (!smi) - return -ENODEV; - - smi->clk_delay = 1500; - smi->cmd_read = 0xb9; - smi->cmd_write = 0xb8; - smi->ops = &rtl8367b_smi_ops; - smi->cpu_port = RTL8367B_CPU_PORT_NUM; - smi->num_ports = RTL8367B_NUM_PORTS; - smi->num_vlan_mc = RTL8367B_NUM_VLANS; - smi->mib_counters = rtl8367b_mib_counters; - smi->num_mib_counters = ARRAY_SIZE(rtl8367b_mib_counters); - - err = rtl8366_smi_init(smi); - if (err) - goto err_free_smi; - - platform_set_drvdata(pdev, smi); - - err = rtl8367b_switch_init(smi); - if (err) - goto err_clear_drvdata; - - return 0; - - err_clear_drvdata: - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - err_free_smi: - kfree(smi); - return err; -} - -static int rtl8367b_remove(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) { - rtl8367b_switch_cleanup(smi); - platform_set_drvdata(pdev, NULL); - rtl8366_smi_cleanup(smi); - kfree(smi); - } - - return 0; -} - -static void rtl8367b_shutdown(struct platform_device *pdev) -{ - struct rtl8366_smi *smi = platform_get_drvdata(pdev); - - if (smi) - rtl8367b_reset_chip(smi); -} - -#ifdef CONFIG_OF -static const struct of_device_id rtl8367b_match[] = { - { .compatible = "realtek,rtl8367b" }, - {}, -}; -MODULE_DEVICE_TABLE(of, rtl8367b_match); -#endif - -static struct platform_driver rtl8367b_driver = { - .driver = { - .name = RTL8367B_DRIVER_NAME, - .owner = THIS_MODULE, -#ifdef CONFIG_OF - .of_match_table = of_match_ptr(rtl8367b_match), -#endif - }, - .probe = rtl8367b_probe, - .remove = rtl8367b_remove, - .shutdown = rtl8367b_shutdown, -}; - -module_platform_driver(rtl8367b_driver); - -MODULE_DESCRIPTION("Realtek RTL8367B ethernet switch driver"); -MODULE_AUTHOR("Gabor Juhos "); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME); - diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c deleted file mode 100644 index e8a684742c..0000000000 --- a/target/linux/generic/files/drivers/net/phy/swconfig.c +++ /dev/null @@ -1,1256 +0,0 @@ -/* - * swconfig.c: Switch configuration API - * - * Copyright (C) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SWCONFIG_DEVNAME "switch%d" - -#include "swconfig_leds.c" - -MODULE_AUTHOR("Felix Fietkau "); -MODULE_LICENSE("GPL"); - -static int swdev_id; -static struct list_head swdevs; -static DEFINE_MUTEX(swdevs_lock); -struct swconfig_callback; - -struct swconfig_callback { - struct sk_buff *msg; - struct genlmsghdr *hdr; - struct genl_info *info; - int cmd; - - /* callback for filling in the message data */ - int (*fill)(struct swconfig_callback *cb, void *arg); - - /* callback for closing the message before sending it */ - int (*close)(struct swconfig_callback *cb, void *arg); - - struct nlattr *nest[4]; - int args[4]; -}; - -/* defaults */ - -static int -swconfig_get_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - int ret; - if (val->port_vlan >= dev->vlans) - return -EINVAL; - - if (!dev->ops->get_vlan_ports) - return -EOPNOTSUPP; - - ret = dev->ops->get_vlan_ports(dev, val); - return ret; -} - -static int -swconfig_set_vlan_ports(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct switch_port *ports = val->value.ports; - const struct switch_dev_ops *ops = dev->ops; - int i; - - if (val->port_vlan >= dev->vlans) - return -EINVAL; - - /* validate ports */ - if (val->len > dev->ports) - return -EINVAL; - - if (!ops->set_vlan_ports) - return -EOPNOTSUPP; - - for (i = 0; i < val->len; i++) { - if (ports[i].id >= dev->ports) - return -EINVAL; - - if (ops->set_port_pvid && - !(ports[i].flags & (1 << SWITCH_PORT_FLAG_TAGGED))) - ops->set_port_pvid(dev, ports[i].id, val->port_vlan); - } - - return ops->set_vlan_ports(dev, val); -} - -static int -swconfig_set_pvid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - if (val->port_vlan >= dev->ports) - return -EINVAL; - - if (!dev->ops->set_port_pvid) - return -EOPNOTSUPP; - - return dev->ops->set_port_pvid(dev, val->port_vlan, val->value.i); -} - -static int -swconfig_get_pvid(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - if (val->port_vlan >= dev->ports) - return -EINVAL; - - if (!dev->ops->get_port_pvid) - return -EOPNOTSUPP; - - return dev->ops->get_port_pvid(dev, val->port_vlan, &val->value.i); -} - -static int -swconfig_set_link(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - if (!dev->ops->set_port_link) - return -EOPNOTSUPP; - - return dev->ops->set_port_link(dev, val->port_vlan, val->value.link); -} - -static int -swconfig_get_link(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - struct switch_port_link *link = val->value.link; - - if (val->port_vlan >= dev->ports) - return -EINVAL; - - if (!dev->ops->get_port_link) - return -EOPNOTSUPP; - - memset(link, 0, sizeof(*link)); - return dev->ops->get_port_link(dev, val->port_vlan, link); -} - -static int -swconfig_apply_config(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - /* don't complain if not supported by the switch driver */ - if (!dev->ops->apply_config) - return 0; - - return dev->ops->apply_config(dev); -} - -static int -swconfig_reset_switch(struct switch_dev *dev, const struct switch_attr *attr, - struct switch_val *val) -{ - /* don't complain if not supported by the switch driver */ - if (!dev->ops->reset_switch) - return 0; - - return dev->ops->reset_switch(dev); -} - -enum global_defaults { - GLOBAL_APPLY, - GLOBAL_RESET, -}; - -enum vlan_defaults { - VLAN_PORTS, -}; - -enum port_defaults { - PORT_PVID, - PORT_LINK, -}; - -static struct switch_attr default_global[] = { - [GLOBAL_APPLY] = { - .type = SWITCH_TYPE_NOVAL, - .name = "apply", - .description = "Activate changes in the hardware", - .set = swconfig_apply_config, - }, - [GLOBAL_RESET] = { - .type = SWITCH_TYPE_NOVAL, - .name = "reset", - .description = "Reset the switch", - .set = swconfig_reset_switch, - } -}; - -static struct switch_attr default_port[] = { - [PORT_PVID] = { - .type = SWITCH_TYPE_INT, - .name = "pvid", - .description = "Primary VLAN ID", - .set = swconfig_set_pvid, - .get = swconfig_get_pvid, - }, - [PORT_LINK] = { - .type = SWITCH_TYPE_LINK, - .name = "link", - .description = "Get port link information", - .set = swconfig_set_link, - .get = swconfig_get_link, - } -}; - -static struct switch_attr default_vlan[] = { - [VLAN_PORTS] = { - .type = SWITCH_TYPE_PORTS, - .name = "ports", - .description = "VLAN port mapping", - .set = swconfig_set_vlan_ports, - .get = swconfig_get_vlan_ports, - }, -}; - -static const struct switch_attr * -swconfig_find_attr_by_name(const struct switch_attrlist *alist, - const char *name) -{ - int i; - - for (i = 0; i < alist->n_attr; i++) - if (strcmp(name, alist->attr[i].name) == 0) - return &alist->attr[i]; - - return NULL; -} - -static void swconfig_defaults_init(struct switch_dev *dev) -{ - const struct switch_dev_ops *ops = dev->ops; - - dev->def_global = 0; - dev->def_vlan = 0; - dev->def_port = 0; - - if (ops->get_vlan_ports || ops->set_vlan_ports) - set_bit(VLAN_PORTS, &dev->def_vlan); - - if (ops->get_port_pvid || ops->set_port_pvid) - set_bit(PORT_PVID, &dev->def_port); - - if (ops->get_port_link && - !swconfig_find_attr_by_name(&ops->attr_port, "link")) - set_bit(PORT_LINK, &dev->def_port); - - /* always present, can be no-op */ - set_bit(GLOBAL_APPLY, &dev->def_global); - set_bit(GLOBAL_RESET, &dev->def_global); -} - - -static struct genl_family switch_fam; - -static const struct nla_policy switch_policy[SWITCH_ATTR_MAX+1] = { - [SWITCH_ATTR_ID] = { .type = NLA_U32 }, - [SWITCH_ATTR_OP_ID] = { .type = NLA_U32 }, - [SWITCH_ATTR_OP_PORT] = { .type = NLA_U32 }, - [SWITCH_ATTR_OP_VLAN] = { .type = NLA_U32 }, - [SWITCH_ATTR_OP_VALUE_INT] = { .type = NLA_U32 }, - [SWITCH_ATTR_OP_VALUE_STR] = { .type = NLA_NUL_STRING }, - [SWITCH_ATTR_OP_VALUE_PORTS] = { .type = NLA_NESTED }, - [SWITCH_ATTR_TYPE] = { .type = NLA_U32 }, -}; - -static const struct nla_policy port_policy[SWITCH_PORT_ATTR_MAX+1] = { - [SWITCH_PORT_ID] = { .type = NLA_U32 }, - [SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG }, -}; - -static struct nla_policy link_policy[SWITCH_LINK_ATTR_MAX] = { - [SWITCH_LINK_FLAG_DUPLEX] = { .type = NLA_FLAG }, - [SWITCH_LINK_FLAG_ANEG] = { .type = NLA_FLAG }, - [SWITCH_LINK_SPEED] = { .type = NLA_U32 }, -}; - -static inline void -swconfig_lock(void) -{ - mutex_lock(&swdevs_lock); -} - -static inline void -swconfig_unlock(void) -{ - mutex_unlock(&swdevs_lock); -} - -static struct switch_dev * -swconfig_get_dev(struct genl_info *info) -{ - struct switch_dev *dev = NULL; - struct switch_dev *p; - int id; - - if (!info->attrs[SWITCH_ATTR_ID]) - goto done; - - id = nla_get_u32(info->attrs[SWITCH_ATTR_ID]); - swconfig_lock(); - list_for_each_entry(p, &swdevs, dev_list) { - if (id != p->id) - continue; - - dev = p; - break; - } - if (dev) - mutex_lock(&dev->sw_mutex); - else - pr_debug("device %d not found\n", id); - swconfig_unlock(); -done: - return dev; -} - -static inline void -swconfig_put_dev(struct switch_dev *dev) -{ - mutex_unlock(&dev->sw_mutex); -} - -static int -swconfig_dump_attr(struct swconfig_callback *cb, void *arg) -{ - struct switch_attr *op = arg; - struct genl_info *info = cb->info; - struct sk_buff *msg = cb->msg; - int id = cb->args[0]; - void *hdr; - - hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, - NLM_F_MULTI, SWITCH_CMD_NEW_ATTR); - if (IS_ERR(hdr)) - return -1; - - if (nla_put_u32(msg, SWITCH_ATTR_OP_ID, id)) - goto nla_put_failure; - if (nla_put_u32(msg, SWITCH_ATTR_OP_TYPE, op->type)) - goto nla_put_failure; - if (nla_put_string(msg, SWITCH_ATTR_OP_NAME, op->name)) - goto nla_put_failure; - if (op->description) - if (nla_put_string(msg, SWITCH_ATTR_OP_DESCRIPTION, - op->description)) - goto nla_put_failure; - - genlmsg_end(msg, hdr); - return msg->len; -nla_put_failure: - genlmsg_cancel(msg, hdr); - return -EMSGSIZE; -} - -/* spread multipart messages across multiple message buffers */ -static int -swconfig_send_multipart(struct swconfig_callback *cb, void *arg) -{ - struct genl_info *info = cb->info; - int restart = 0; - int err; - - do { - if (!cb->msg) { - cb->msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); - if (cb->msg == NULL) - goto error; - } - - if (!(cb->fill(cb, arg) < 0)) - break; - - /* fill failed, check if this was already the second attempt */ - if (restart) - goto error; - - /* try again in a new message, send the current one */ - restart = 1; - if (cb->close) { - if (cb->close(cb, arg) < 0) - goto error; - } - err = genlmsg_reply(cb->msg, info); - cb->msg = NULL; - if (err < 0) - goto error; - - } while (restart); - - return 0; - -error: - if (cb->msg) - nlmsg_free(cb->msg); - return -1; -} - -static int -swconfig_list_attrs(struct sk_buff *skb, struct genl_info *info) -{ - struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); - const struct switch_attrlist *alist; - struct switch_dev *dev; - struct swconfig_callback cb; - int err = -EINVAL; - int i; - - /* defaults */ - struct switch_attr *def_list; - unsigned long *def_active; - int n_def; - - dev = swconfig_get_dev(info); - if (!dev) - return -EINVAL; - - switch (hdr->cmd) { - case SWITCH_CMD_LIST_GLOBAL: - alist = &dev->ops->attr_global; - def_list = default_global; - def_active = &dev->def_global; - n_def = ARRAY_SIZE(default_global); - break; - case SWITCH_CMD_LIST_VLAN: - alist = &dev->ops->attr_vlan; - def_list = default_vlan; - def_active = &dev->def_vlan; - n_def = ARRAY_SIZE(default_vlan); - break; - case SWITCH_CMD_LIST_PORT: - alist = &dev->ops->attr_port; - def_list = default_port; - def_active = &dev->def_port; - n_def = ARRAY_SIZE(default_port); - break; - default: - WARN_ON(1); - goto out; - } - - memset(&cb, 0, sizeof(cb)); - cb.info = info; - cb.fill = swconfig_dump_attr; - for (i = 0; i < alist->n_attr; i++) { - if (alist->attr[i].disabled) - continue; - cb.args[0] = i; - err = swconfig_send_multipart(&cb, (void *) &alist->attr[i]); - if (err < 0) - goto error; - } - - /* defaults */ - for (i = 0; i < n_def; i++) { - if (!test_bit(i, def_active)) - continue; - cb.args[0] = SWITCH_ATTR_DEFAULTS_OFFSET + i; - err = swconfig_send_multipart(&cb, (void *) &def_list[i]); - if (err < 0) - goto error; - } - swconfig_put_dev(dev); - - if (!cb.msg) - return 0; - - return genlmsg_reply(cb.msg, info); - -error: - if (cb.msg) - nlmsg_free(cb.msg); -out: - swconfig_put_dev(dev); - return err; -} - -static const struct switch_attr * -swconfig_lookup_attr(struct switch_dev *dev, struct genl_info *info, - struct switch_val *val) -{ - struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); - const struct switch_attrlist *alist; - const struct switch_attr *attr = NULL; - unsigned int attr_id; - - /* defaults */ - struct switch_attr *def_list; - unsigned long *def_active; - int n_def; - - if (!info->attrs[SWITCH_ATTR_OP_ID]) - goto done; - - switch (hdr->cmd) { - case SWITCH_CMD_SET_GLOBAL: - case SWITCH_CMD_GET_GLOBAL: - alist = &dev->ops->attr_global; - def_list = default_global; - def_active = &dev->def_global; - n_def = ARRAY_SIZE(default_global); - break; - case SWITCH_CMD_SET_VLAN: - case SWITCH_CMD_GET_VLAN: - alist = &dev->ops->attr_vlan; - def_list = default_vlan; - def_active = &dev->def_vlan; - n_def = ARRAY_SIZE(default_vlan); - if (!info->attrs[SWITCH_ATTR_OP_VLAN]) - goto done; - val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_VLAN]); - if (val->port_vlan >= dev->vlans) - goto done; - break; - case SWITCH_CMD_SET_PORT: - case SWITCH_CMD_GET_PORT: - alist = &dev->ops->attr_port; - def_list = default_port; - def_active = &dev->def_port; - n_def = ARRAY_SIZE(default_port); - if (!info->attrs[SWITCH_ATTR_OP_PORT]) - goto done; - val->port_vlan = nla_get_u32(info->attrs[SWITCH_ATTR_OP_PORT]); - if (val->port_vlan >= dev->ports) - goto done; - break; - default: - WARN_ON(1); - goto done; - } - - if (!alist) - goto done; - - attr_id = nla_get_u32(info->attrs[SWITCH_ATTR_OP_ID]); - if (attr_id >= SWITCH_ATTR_DEFAULTS_OFFSET) { - attr_id -= SWITCH_ATTR_DEFAULTS_OFFSET; - if (attr_id >= n_def) - goto done; - if (!test_bit(attr_id, def_active)) - goto done; - attr = &def_list[attr_id]; - } else { - if (attr_id >= alist->n_attr) - goto done; - attr = &alist->attr[attr_id]; - } - - if (attr->disabled) - attr = NULL; - -done: - if (!attr) - pr_debug("attribute lookup failed\n"); - val->attr = attr; - return attr; -} - -static int -swconfig_parse_ports(struct sk_buff *msg, struct nlattr *head, - struct switch_val *val, int max) -{ - struct nlattr *nla; - int rem; - - val->len = 0; - nla_for_each_nested(nla, head, rem) { - struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1]; - struct switch_port *port; - - if (val->len >= max) - return -EINVAL; - - port = &val->value.ports[val->len]; - -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) - if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, - port_policy)) -#else - if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla, - port_policy, NULL)) -#endif - return -EINVAL; - - if (!tb[SWITCH_PORT_ID]) - return -EINVAL; - - port->id = nla_get_u32(tb[SWITCH_PORT_ID]); - if (tb[SWITCH_PORT_FLAG_TAGGED]) - port->flags |= (1 << SWITCH_PORT_FLAG_TAGGED); - val->len++; - } - - return 0; -} - -static int -swconfig_parse_link(struct sk_buff *msg, struct nlattr *nla, - struct switch_port_link *link) -{ - struct nlattr *tb[SWITCH_LINK_ATTR_MAX + 1]; - -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) - if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy)) -#else - if (nla_parse_nested(tb, SWITCH_LINK_ATTR_MAX, nla, link_policy, NULL)) -#endif - return -EINVAL; - - link->duplex = !!tb[SWITCH_LINK_FLAG_DUPLEX]; - link->aneg = !!tb[SWITCH_LINK_FLAG_ANEG]; - link->speed = nla_get_u32(tb[SWITCH_LINK_SPEED]); - - return 0; -} - -static int -swconfig_set_attr(struct sk_buff *skb, struct genl_info *info) -{ - const struct switch_attr *attr; - struct switch_dev *dev; - struct switch_val val; - int err = -EINVAL; - - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - - dev = swconfig_get_dev(info); - if (!dev) - return -EINVAL; - - memset(&val, 0, sizeof(val)); - attr = swconfig_lookup_attr(dev, info, &val); - if (!attr || !attr->set) - goto error; - - val.attr = attr; - switch (attr->type) { - case SWITCH_TYPE_NOVAL: - break; - case SWITCH_TYPE_INT: - if (!info->attrs[SWITCH_ATTR_OP_VALUE_INT]) - goto error; - val.value.i = - nla_get_u32(info->attrs[SWITCH_ATTR_OP_VALUE_INT]); - break; - case SWITCH_TYPE_STRING: - if (!info->attrs[SWITCH_ATTR_OP_VALUE_STR]) - goto error; - val.value.s = - nla_data(info->attrs[SWITCH_ATTR_OP_VALUE_STR]); - break; - case SWITCH_TYPE_PORTS: - val.value.ports = dev->portbuf; - memset(dev->portbuf, 0, - sizeof(struct switch_port) * dev->ports); - - /* TODO: implement multipart? */ - if (info->attrs[SWITCH_ATTR_OP_VALUE_PORTS]) { - err = swconfig_parse_ports(skb, - info->attrs[SWITCH_ATTR_OP_VALUE_PORTS], - &val, dev->ports); - if (err < 0) - goto error; - } else { - val.len = 0; - err = 0; - } - break; - case SWITCH_TYPE_LINK: - val.value.link = &dev->linkbuf; - memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); - - if (info->attrs[SWITCH_ATTR_OP_VALUE_LINK]) { - err = swconfig_parse_link(skb, - info->attrs[SWITCH_ATTR_OP_VALUE_LINK], - val.value.link); - if (err < 0) - goto error; - } else { - val.len = 0; - err = 0; - } - break; - default: - goto error; - } - - err = attr->set(dev, attr, &val); -error: - swconfig_put_dev(dev); - return err; -} - -static int -swconfig_close_portlist(struct swconfig_callback *cb, void *arg) -{ - if (cb->nest[0]) - nla_nest_end(cb->msg, cb->nest[0]); - return 0; -} - -static int -swconfig_send_port(struct swconfig_callback *cb, void *arg) -{ - const struct switch_port *port = arg; - struct nlattr *p = NULL; - - if (!cb->nest[0]) { - cb->nest[0] = nla_nest_start(cb->msg, cb->cmd); - if (!cb->nest[0]) - return -1; - } - - p = nla_nest_start(cb->msg, SWITCH_ATTR_PORT); - if (!p) - goto error; - - if (nla_put_u32(cb->msg, SWITCH_PORT_ID, port->id)) - goto nla_put_failure; - if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { - if (nla_put_flag(cb->msg, SWITCH_PORT_FLAG_TAGGED)) - goto nla_put_failure; - } - - nla_nest_end(cb->msg, p); - return 0; - -nla_put_failure: - nla_nest_cancel(cb->msg, p); -error: - nla_nest_cancel(cb->msg, cb->nest[0]); - return -1; -} - -static int -swconfig_send_ports(struct sk_buff **msg, struct genl_info *info, int attr, - const struct switch_val *val) -{ - struct swconfig_callback cb; - int err = 0; - int i; - - if (!val->value.ports) - return -EINVAL; - - memset(&cb, 0, sizeof(cb)); - cb.cmd = attr; - cb.msg = *msg; - cb.info = info; - cb.fill = swconfig_send_port; - cb.close = swconfig_close_portlist; - - cb.nest[0] = nla_nest_start(cb.msg, cb.cmd); - for (i = 0; i < val->len; i++) { - err = swconfig_send_multipart(&cb, &val->value.ports[i]); - if (err) - goto done; - } - err = val->len; - swconfig_close_portlist(&cb, NULL); - *msg = cb.msg; - -done: - return err; -} - -static int -swconfig_send_link(struct sk_buff *msg, struct genl_info *info, int attr, - const struct switch_port_link *link) -{ - struct nlattr *p = NULL; - int err = 0; - - p = nla_nest_start(msg, attr); - if (link->link) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_LINK)) - goto nla_put_failure; - } - if (link->duplex) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_DUPLEX)) - goto nla_put_failure; - } - if (link->aneg) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_ANEG)) - goto nla_put_failure; - } - if (link->tx_flow) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_TX_FLOW)) - goto nla_put_failure; - } - if (link->rx_flow) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_RX_FLOW)) - goto nla_put_failure; - } - if (nla_put_u32(msg, SWITCH_LINK_SPEED, link->speed)) - goto nla_put_failure; - if (link->eee & ADVERTISED_100baseT_Full) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_100BASET)) - goto nla_put_failure; - } - if (link->eee & ADVERTISED_1000baseT_Full) { - if (nla_put_flag(msg, SWITCH_LINK_FLAG_EEE_1000BASET)) - goto nla_put_failure; - } - nla_nest_end(msg, p); - - return err; - -nla_put_failure: - nla_nest_cancel(msg, p); - return -1; -} - -static int -swconfig_get_attr(struct sk_buff *skb, struct genl_info *info) -{ - struct genlmsghdr *hdr = nlmsg_data(info->nlhdr); - const struct switch_attr *attr; - struct switch_dev *dev; - struct sk_buff *msg = NULL; - struct switch_val val; - int err = -EINVAL; - int cmd = hdr->cmd; - - dev = swconfig_get_dev(info); - if (!dev) - return -EINVAL; - - memset(&val, 0, sizeof(val)); - attr = swconfig_lookup_attr(dev, info, &val); - if (!attr || !attr->get) - goto error; - - if (attr->type == SWITCH_TYPE_PORTS) { - val.value.ports = dev->portbuf; - memset(dev->portbuf, 0, - sizeof(struct switch_port) * dev->ports); - } else if (attr->type == SWITCH_TYPE_LINK) { - val.value.link = &dev->linkbuf; - memset(&dev->linkbuf, 0, sizeof(struct switch_port_link)); - } - - err = attr->get(dev, attr, &val); - if (err) - goto error; - - msg = nlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); - if (!msg) - goto error; - - hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &switch_fam, - 0, cmd); - if (IS_ERR(hdr)) - goto nla_put_failure; - - switch (attr->type) { - case SWITCH_TYPE_INT: - if (nla_put_u32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i)) - goto nla_put_failure; - break; - case SWITCH_TYPE_STRING: - if (nla_put_string(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s)) - goto nla_put_failure; - break; - case SWITCH_TYPE_PORTS: - err = swconfig_send_ports(&msg, info, - SWITCH_ATTR_OP_VALUE_PORTS, &val); - if (err < 0) - goto nla_put_failure; - break; - case SWITCH_TYPE_LINK: - err = swconfig_send_link(msg, info, - SWITCH_ATTR_OP_VALUE_LINK, val.value.link); - if (err < 0) - goto nla_put_failure; - break; - default: - pr_debug("invalid type in attribute\n"); - err = -EINVAL; - goto nla_put_failure; - } - genlmsg_end(msg, hdr); - err = msg->len; - if (err < 0) - goto nla_put_failure; - - swconfig_put_dev(dev); - return genlmsg_reply(msg, info); - -nla_put_failure: - if (msg) - nlmsg_free(msg); -error: - swconfig_put_dev(dev); - if (!err) - err = -ENOMEM; - return err; -} - -static int -swconfig_send_switch(struct sk_buff *msg, u32 pid, u32 seq, int flags, - const struct switch_dev *dev) -{ - struct nlattr *p = NULL, *m = NULL; - void *hdr; - int i; - - hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags, - SWITCH_CMD_NEW_ATTR); - if (IS_ERR(hdr)) - return -1; - - if (nla_put_u32(msg, SWITCH_ATTR_ID, dev->id)) - goto nla_put_failure; - if (nla_put_string(msg, SWITCH_ATTR_DEV_NAME, dev->devname)) - goto nla_put_failure; - if (nla_put_string(msg, SWITCH_ATTR_ALIAS, dev->alias)) - goto nla_put_failure; - if (nla_put_string(msg, SWITCH_ATTR_NAME, dev->name)) - goto nla_put_failure; - if (nla_put_u32(msg, SWITCH_ATTR_VLANS, dev->vlans)) - goto nla_put_failure; - if (nla_put_u32(msg, SWITCH_ATTR_PORTS, dev->ports)) - goto nla_put_failure; - if (nla_put_u32(msg, SWITCH_ATTR_CPU_PORT, dev->cpu_port)) - goto nla_put_failure; - - m = nla_nest_start(msg, SWITCH_ATTR_PORTMAP); - if (!m) - goto nla_put_failure; - for (i = 0; i < dev->ports; i++) { - p = nla_nest_start(msg, SWITCH_ATTR_PORTS); - if (!p) - continue; - if (dev->portmap[i].s) { - if (nla_put_string(msg, SWITCH_PORTMAP_SEGMENT, - dev->portmap[i].s)) - goto nla_put_failure; - if (nla_put_u32(msg, SWITCH_PORTMAP_VIRT, - dev->portmap[i].virt)) - goto nla_put_failure; - } - nla_nest_end(msg, p); - } - nla_nest_end(msg, m); - genlmsg_end(msg, hdr); - return msg->len; -nla_put_failure: - genlmsg_cancel(msg, hdr); - return -EMSGSIZE; -} - -static int swconfig_dump_switches(struct sk_buff *skb, - struct netlink_callback *cb) -{ - struct switch_dev *dev; - int start = cb->args[0]; - int idx = 0; - - swconfig_lock(); - list_for_each_entry(dev, &swdevs, dev_list) { - if (++idx <= start) - continue; - if (swconfig_send_switch(skb, NETLINK_CB(cb->skb).portid, - cb->nlh->nlmsg_seq, NLM_F_MULTI, - dev) < 0) - break; - } - swconfig_unlock(); - cb->args[0] = idx; - - return skb->len; -} - -static int -swconfig_done(struct netlink_callback *cb) -{ - return 0; -} - -static struct genl_ops swconfig_ops[] = { - { - .cmd = SWITCH_CMD_LIST_GLOBAL, - .doit = swconfig_list_attrs, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_LIST_VLAN, - .doit = swconfig_list_attrs, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_LIST_PORT, - .doit = swconfig_list_attrs, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_GET_GLOBAL, - .doit = swconfig_get_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_GET_VLAN, - .doit = swconfig_get_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_GET_PORT, - .doit = swconfig_get_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_SET_GLOBAL, - .flags = GENL_ADMIN_PERM, - .doit = swconfig_set_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_SET_VLAN, - .flags = GENL_ADMIN_PERM, - .doit = swconfig_set_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_SET_PORT, - .flags = GENL_ADMIN_PERM, - .doit = swconfig_set_attr, - .policy = switch_policy, - }, - { - .cmd = SWITCH_CMD_GET_SWITCH, - .dumpit = swconfig_dump_switches, - .policy = switch_policy, - .done = swconfig_done, - } -}; - -static struct genl_family switch_fam = { -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) - .id = GENL_ID_GENERATE, -#endif - .name = "switch", - .hdrsize = 0, - .version = 1, - .maxattr = SWITCH_ATTR_MAX, - .module = THIS_MODULE, - .ops = swconfig_ops, - .n_ops = ARRAY_SIZE(swconfig_ops), -}; - -#ifdef CONFIG_OF -void -of_switch_load_portmap(struct switch_dev *dev) -{ - struct device_node *port; - - if (!dev->of_node) - return; - - for_each_child_of_node(dev->of_node, port) { - const __be32 *prop; - const char *segment; - int size, phys; - - if (!of_device_is_compatible(port, "swconfig,port")) - continue; - - if (of_property_read_string(port, "swconfig,segment", &segment)) - continue; - - prop = of_get_property(port, "swconfig,portmap", &size); - if (!prop) - continue; - - if (size != (2 * sizeof(*prop))) { - pr_err("%s: failed to parse port mapping\n", - port->name); - continue; - } - - phys = be32_to_cpup(prop++); - if ((phys < 0) | (phys >= dev->ports)) { - pr_err("%s: physical port index out of range\n", - port->name); - continue; - } - - dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); - dev->portmap[phys].virt = be32_to_cpup(prop); - pr_debug("Found port: %s, physical: %d, virtual: %d\n", - segment, phys, dev->portmap[phys].virt); - } -} -#endif - -int -register_switch(struct switch_dev *dev, struct net_device *netdev) -{ - struct switch_dev *sdev; - const int max_switches = 8 * sizeof(unsigned long); - unsigned long in_use = 0; - int err; - int i; - - INIT_LIST_HEAD(&dev->dev_list); - if (netdev) { - dev->netdev = netdev; - if (!dev->alias) - dev->alias = netdev->name; - } - BUG_ON(!dev->alias); - - /* Make sure swdev_id doesn't overflow */ - if (swdev_id == INT_MAX) { - return -ENOMEM; - } - - if (dev->ports > 0) { - dev->portbuf = kzalloc(sizeof(struct switch_port) * - dev->ports, GFP_KERNEL); - if (!dev->portbuf) - return -ENOMEM; - dev->portmap = kzalloc(sizeof(struct switch_portmap) * - dev->ports, GFP_KERNEL); - if (!dev->portmap) { - kfree(dev->portbuf); - return -ENOMEM; - } - } - swconfig_defaults_init(dev); - mutex_init(&dev->sw_mutex); - swconfig_lock(); - dev->id = ++swdev_id; - - list_for_each_entry(sdev, &swdevs, dev_list) { - if (!sscanf(sdev->devname, SWCONFIG_DEVNAME, &i)) - continue; - if (i < 0 || i > max_switches) - continue; - - set_bit(i, &in_use); - } - i = find_first_zero_bit(&in_use, max_switches); - - if (i == max_switches) { - swconfig_unlock(); - return -ENFILE; - } - -#ifdef CONFIG_OF - if (dev->ports) - of_switch_load_portmap(dev); -#endif - - /* fill device name */ - snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); - - list_add_tail(&dev->dev_list, &swdevs); - swconfig_unlock(); - - err = swconfig_create_led_trigger(dev); - if (err) - return err; - - return 0; -} -EXPORT_SYMBOL_GPL(register_switch); - -void -unregister_switch(struct switch_dev *dev) -{ - swconfig_destroy_led_trigger(dev); - kfree(dev->portbuf); - mutex_lock(&dev->sw_mutex); - swconfig_lock(); - list_del(&dev->dev_list); - swconfig_unlock(); - mutex_unlock(&dev->sw_mutex); -} -EXPORT_SYMBOL_GPL(unregister_switch); - -int -switch_generic_set_link(struct switch_dev *dev, int port, - struct switch_port_link *link) -{ - if (WARN_ON(!dev->ops->phy_write16)) - return -ENOTSUPP; - - /* Generic implementation */ - if (link->aneg) { - dev->ops->phy_write16(dev, port, MII_BMCR, 0x0000); - dev->ops->phy_write16(dev, port, MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART); - } else { - u16 bmcr = 0; - - if (link->duplex) - bmcr |= BMCR_FULLDPLX; - - switch (link->speed) { - case SWITCH_PORT_SPEED_10: - break; - case SWITCH_PORT_SPEED_100: - bmcr |= BMCR_SPEED100; - break; - case SWITCH_PORT_SPEED_1000: - bmcr |= BMCR_SPEED1000; - break; - default: - return -ENOTSUPP; - } - - dev->ops->phy_write16(dev, port, MII_BMCR, bmcr); - } - - return 0; -} - -static int __init -swconfig_init(void) -{ - INIT_LIST_HEAD(&swdevs); - -#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0) - return genl_register_family_with_ops(&switch_fam, swconfig_ops); -#else - return genl_register_family(&switch_fam); -#endif -} - -static void __exit -swconfig_exit(void) -{ - genl_unregister_family(&switch_fam); -} - -module_init(swconfig_init); -module_exit(swconfig_exit); diff --git a/target/linux/generic/files/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c deleted file mode 100644 index 91824b7cf6..0000000000 --- a/target/linux/generic/files/drivers/net/phy/swconfig_leds.c +++ /dev/null @@ -1,556 +0,0 @@ -/* - * swconfig_led.c: LED trigger support for the switch configuration API - * - * Copyright (C) 2011 Gabor Juhos - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - */ - -#ifdef CONFIG_SWCONFIG_LEDS - -#include -#include -#include -#include - -#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) -#define SWCONFIG_LED_NUM_PORTS 32 - -#define SWCONFIG_LED_PORT_SPEED_NA 0x01 /* unknown speed */ -#define SWCONFIG_LED_PORT_SPEED_10 0x02 /* 10 Mbps */ -#define SWCONFIG_LED_PORT_SPEED_100 0x04 /* 100 Mbps */ -#define SWCONFIG_LED_PORT_SPEED_1000 0x08 /* 1000 Mbps */ -#define SWCONFIG_LED_PORT_SPEED_ALL (SWCONFIG_LED_PORT_SPEED_NA | \ - SWCONFIG_LED_PORT_SPEED_10 | \ - SWCONFIG_LED_PORT_SPEED_100 | \ - SWCONFIG_LED_PORT_SPEED_1000) - -#define SWCONFIG_LED_MODE_LINK 0x01 -#define SWCONFIG_LED_MODE_TX 0x02 -#define SWCONFIG_LED_MODE_RX 0x04 -#define SWCONFIG_LED_MODE_TXRX (SWCONFIG_LED_MODE_TX | \ - SWCONFIG_LED_MODE_RX) -#define SWCONFIG_LED_MODE_ALL (SWCONFIG_LED_MODE_LINK | \ - SWCONFIG_LED_MODE_TX | \ - SWCONFIG_LED_MODE_RX) - -struct switch_led_trigger { - struct led_trigger trig; - struct switch_dev *swdev; - - struct delayed_work sw_led_work; - u32 port_mask; - u32 port_link; - unsigned long long port_tx_traffic[SWCONFIG_LED_NUM_PORTS]; - unsigned long long port_rx_traffic[SWCONFIG_LED_NUM_PORTS]; - u8 link_speed[SWCONFIG_LED_NUM_PORTS]; -}; - -struct swconfig_trig_data { - struct led_classdev *led_cdev; - struct switch_dev *swdev; - - rwlock_t lock; - u32 port_mask; - - bool prev_link; - unsigned long prev_traffic; - enum led_brightness prev_brightness; - u8 mode; - u8 speed_mask; -}; - -static void -swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, - enum led_brightness brightness) -{ - led_set_brightness(trig_data->led_cdev, brightness); - trig_data->prev_brightness = brightness; -} - -static void -swconfig_trig_update_port_mask(struct led_trigger *trigger) -{ - struct list_head *entry; - struct switch_led_trigger *sw_trig; - u32 port_mask; - - if (!trigger) - return; - - sw_trig = (void *) trigger; - - port_mask = 0; - read_lock(&trigger->leddev_list_lock); - list_for_each(entry, &trigger->led_cdevs) { - struct led_classdev *led_cdev; - struct swconfig_trig_data *trig_data; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); - trig_data = led_cdev->trigger_data; - if (trig_data) { - read_lock(&trig_data->lock); - port_mask |= trig_data->port_mask; - read_unlock(&trig_data->lock); - } - } - read_unlock(&trigger->leddev_list_lock); - - sw_trig->port_mask = port_mask; - - if (port_mask) - schedule_delayed_work(&sw_trig->sw_led_work, - SWCONFIG_LED_TIMER_INTERVAL); - else - cancel_delayed_work_sync(&sw_trig->sw_led_work); -} - -static ssize_t -swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - unsigned long port_mask; - int ret; - bool changed; - - ret = kstrtoul(buf, 0, &port_mask); - if (ret) - return ret; - - write_lock(&trig_data->lock); - changed = (trig_data->port_mask != port_mask); - trig_data->port_mask = port_mask; - write_unlock(&trig_data->lock); - - if (changed) { - if (port_mask == 0) - swconfig_trig_set_brightness(trig_data, LED_OFF); - - swconfig_trig_update_port_mask(led_cdev->trigger); - } - - return size; -} - -static ssize_t -swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - u32 port_mask; - - read_lock(&trig_data->lock); - port_mask = trig_data->port_mask; - read_unlock(&trig_data->lock); - - sprintf(buf, "%#x\n", port_mask); - - return strlen(buf) + 1; -} - -static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, - swconfig_trig_port_mask_store); - -/* speed_mask file handler - display value */ -static ssize_t swconfig_trig_speed_mask_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - u8 speed_mask; - - read_lock(&trig_data->lock); - speed_mask = trig_data->speed_mask; - read_unlock(&trig_data->lock); - - sprintf(buf, "%#x\n", speed_mask); - - return strlen(buf) + 1; -} - -/* speed_mask file handler - store value */ -static ssize_t swconfig_trig_speed_mask_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - u8 speed_mask; - int ret; - - ret = kstrtou8(buf, 0, &speed_mask); - if (ret) - return ret; - - write_lock(&trig_data->lock); - trig_data->speed_mask = speed_mask & SWCONFIG_LED_PORT_SPEED_ALL; - write_unlock(&trig_data->lock); - - return size; -} - -/* speed_mask special file */ -static DEVICE_ATTR(speed_mask, 0644, swconfig_trig_speed_mask_show, - swconfig_trig_speed_mask_store); - -static ssize_t swconfig_trig_mode_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - u8 mode; - - read_lock(&trig_data->lock); - mode = trig_data->mode; - read_unlock(&trig_data->lock); - - if (mode == 0) { - strcpy(buf, "none\n"); - } else { - if (mode & SWCONFIG_LED_MODE_LINK) - strcat(buf, "link "); - if (mode & SWCONFIG_LED_MODE_TX) - strcat(buf, "tx "); - if (mode & SWCONFIG_LED_MODE_RX) - strcat(buf, "rx "); - strcat(buf, "\n"); - } - - return strlen(buf)+1; -} - -static ssize_t swconfig_trig_mode_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct swconfig_trig_data *trig_data = led_cdev->trigger_data; - char copybuf[128]; - int new_mode = -1; - char *p, *token; - - /* take a copy since we don't want to trash the inbound buffer when using strsep */ - strncpy(copybuf, buf, sizeof(copybuf)); - copybuf[sizeof(copybuf) - 1] = 0; - p = copybuf; - - while ((token = strsep(&p, " \t\n")) != NULL) { - if (!*token) - continue; - - if (new_mode < 0) - new_mode = 0; - - if (!strcmp(token, "none")) - new_mode = 0; - else if (!strcmp(token, "tx")) - new_mode |= SWCONFIG_LED_MODE_TX; - else if (!strcmp(token, "rx")) - new_mode |= SWCONFIG_LED_MODE_RX; - else if (!strcmp(token, "link")) - new_mode |= SWCONFIG_LED_MODE_LINK; - else - return -EINVAL; - } - - if (new_mode < 0) - return -EINVAL; - - write_lock(&trig_data->lock); - trig_data->mode = (u8)new_mode; - write_unlock(&trig_data->lock); - - return size; -} - -/* mode special file */ -static DEVICE_ATTR(mode, 0644, swconfig_trig_mode_show, - swconfig_trig_mode_store); - -static void -swconfig_trig_activate(struct led_classdev *led_cdev) -{ - struct switch_led_trigger *sw_trig; - struct swconfig_trig_data *trig_data; - int err; - - if (led_cdev->trigger->activate != swconfig_trig_activate) - return; - - trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); - if (!trig_data) - return; - - sw_trig = (void *) led_cdev->trigger; - - rwlock_init(&trig_data->lock); - trig_data->led_cdev = led_cdev; - trig_data->swdev = sw_trig->swdev; - trig_data->speed_mask = SWCONFIG_LED_PORT_SPEED_ALL; - trig_data->mode = SWCONFIG_LED_MODE_ALL; - led_cdev->trigger_data = trig_data; - - err = device_create_file(led_cdev->dev, &dev_attr_port_mask); - if (err) - goto err_free; - - err = device_create_file(led_cdev->dev, &dev_attr_speed_mask); - if (err) - goto err_dev_free; - - err = device_create_file(led_cdev->dev, &dev_attr_mode); - if (err) - goto err_mode_free; - - return; - -err_mode_free: - device_remove_file(led_cdev->dev, &dev_attr_speed_mask); - -err_dev_free: - device_remove_file(led_cdev->dev, &dev_attr_port_mask); - -err_free: - led_cdev->trigger_data = NULL; - kfree(trig_data); -} - -static void -swconfig_trig_deactivate(struct led_classdev *led_cdev) -{ - struct swconfig_trig_data *trig_data; - - swconfig_trig_update_port_mask(led_cdev->trigger); - - trig_data = (void *) led_cdev->trigger_data; - if (trig_data) { - device_remove_file(led_cdev->dev, &dev_attr_port_mask); - device_remove_file(led_cdev->dev, &dev_attr_speed_mask); - device_remove_file(led_cdev->dev, &dev_attr_mode); - kfree(trig_data); - } -} - -/* - * link off -> led off (can't be any other reason to turn it on) - * link on: - * mode link: led on by default only if speed matches, else off - * mode txrx: blink only if speed matches, else off - */ -static void -swconfig_trig_led_event(struct switch_led_trigger *sw_trig, - struct led_classdev *led_cdev) -{ - struct swconfig_trig_data *trig_data; - u32 port_mask; - bool link; - u8 speed_mask, mode; - enum led_brightness led_base, led_blink; - - trig_data = led_cdev->trigger_data; - if (!trig_data) - return; - - read_lock(&trig_data->lock); - port_mask = trig_data->port_mask; - speed_mask = trig_data->speed_mask; - mode = trig_data->mode; - read_unlock(&trig_data->lock); - - link = !!(sw_trig->port_link & port_mask); - if (!link) { - if (trig_data->prev_brightness != LED_OFF) - swconfig_trig_set_brightness(trig_data, LED_OFF); /* and stop */ - } - else { - unsigned long traffic; - int speedok; /* link speed flag */ - int i; - - led_base = LED_FULL; - led_blink = LED_OFF; - traffic = 0; - speedok = 0; - for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { - if (port_mask & (1 << i)) { - if (sw_trig->link_speed[i] & speed_mask) { - traffic += ((mode & SWCONFIG_LED_MODE_TX) ? - sw_trig->port_tx_traffic[i] : 0) + - ((mode & SWCONFIG_LED_MODE_RX) ? - sw_trig->port_rx_traffic[i] : 0); - speedok = 1; - } - } - } - - if (speedok) { - /* At least one port speed matches speed_mask */ - if (!(mode & SWCONFIG_LED_MODE_LINK)) { - led_base = LED_OFF; - led_blink = LED_FULL; - } - - if (trig_data->prev_brightness != led_base) - swconfig_trig_set_brightness(trig_data, - led_base); - else if (traffic != trig_data->prev_traffic) - swconfig_trig_set_brightness(trig_data, - led_blink); - } else if (trig_data->prev_brightness != LED_OFF) - swconfig_trig_set_brightness(trig_data, LED_OFF); - - trig_data->prev_traffic = traffic; - } - - trig_data->prev_link = link; -} - -static void -swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) -{ - struct list_head *entry; - struct led_trigger *trigger; - - trigger = &sw_trig->trig; - read_lock(&trigger->leddev_list_lock); - list_for_each(entry, &trigger->led_cdevs) { - struct led_classdev *led_cdev; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); - swconfig_trig_led_event(sw_trig, led_cdev); - } - read_unlock(&trigger->leddev_list_lock); -} - -static void -swconfig_led_work_func(struct work_struct *work) -{ - struct switch_led_trigger *sw_trig; - struct switch_dev *swdev; - u32 port_mask; - u32 link; - int i; - - sw_trig = container_of(work, struct switch_led_trigger, - sw_led_work.work); - - port_mask = sw_trig->port_mask; - swdev = sw_trig->swdev; - - link = 0; - for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { - u32 port_bit; - - sw_trig->link_speed[i] = 0; - - port_bit = BIT(i); - if ((port_mask & port_bit) == 0) - continue; - - if (swdev->ops->get_port_link) { - struct switch_port_link port_link; - - memset(&port_link, '\0', sizeof(port_link)); - swdev->ops->get_port_link(swdev, i, &port_link); - - if (port_link.link) { - link |= port_bit; - switch (port_link.speed) { - case SWITCH_PORT_SPEED_UNKNOWN: - sw_trig->link_speed[i] = - SWCONFIG_LED_PORT_SPEED_NA; - break; - case SWITCH_PORT_SPEED_10: - sw_trig->link_speed[i] = - SWCONFIG_LED_PORT_SPEED_10; - break; - case SWITCH_PORT_SPEED_100: - sw_trig->link_speed[i] = - SWCONFIG_LED_PORT_SPEED_100; - break; - case SWITCH_PORT_SPEED_1000: - sw_trig->link_speed[i] = - SWCONFIG_LED_PORT_SPEED_1000; - break; - } - } - } - - if (swdev->ops->get_port_stats) { - struct switch_port_stats port_stats; - - memset(&port_stats, '\0', sizeof(port_stats)); - swdev->ops->get_port_stats(swdev, i, &port_stats); - sw_trig->port_tx_traffic[i] = port_stats.tx_bytes; - sw_trig->port_rx_traffic[i] = port_stats.rx_bytes; - } - } - - sw_trig->port_link = link; - - swconfig_trig_update_leds(sw_trig); - - schedule_delayed_work(&sw_trig->sw_led_work, - SWCONFIG_LED_TIMER_INTERVAL); -} - -static int -swconfig_create_led_trigger(struct switch_dev *swdev) -{ - struct switch_led_trigger *sw_trig; - int err; - - if (!swdev->ops->get_port_link) - return 0; - - sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); - if (!sw_trig) - return -ENOMEM; - - sw_trig->swdev = swdev; - sw_trig->trig.name = swdev->devname; - sw_trig->trig.activate = swconfig_trig_activate; - sw_trig->trig.deactivate = swconfig_trig_deactivate; - - INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); - - err = led_trigger_register(&sw_trig->trig); - if (err) - goto err_free; - - swdev->led_trigger = sw_trig; - - return 0; - -err_free: - kfree(sw_trig); - return err; -} - -static void -swconfig_destroy_led_trigger(struct switch_dev *swdev) -{ - struct switch_led_trigger *sw_trig; - - sw_trig = swdev->led_trigger; - if (sw_trig) { - cancel_delayed_work_sync(&sw_trig->sw_led_work); - led_trigger_unregister(&sw_trig->trig); - kfree(sw_trig); - } -} - -#else /* SWCONFIG_LEDS */ -static inline int -swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } - -static inline void -swconfig_destroy_led_trigger(struct switch_dev *swdev) { } -#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files/include/linux/ar8216_platform.h b/target/linux/generic/files/include/linux/ar8216_platform.h deleted file mode 100644 index 24bc442a26..0000000000 --- a/target/linux/generic/files/include/linux/ar8216_platform.h +++ /dev/null @@ -1,133 +0,0 @@ -/* - * AR8216 switch driver platform data - * - * Copyright (C) 2012 Gabor Juhos - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef AR8216_PLATFORM_H -#define AR8216_PLATFORM_H - -enum ar8327_pad_mode { - AR8327_PAD_NC = 0, - AR8327_PAD_MAC2MAC_MII, - AR8327_PAD_MAC2MAC_GMII, - AR8327_PAD_MAC_SGMII, - AR8327_PAD_MAC2PHY_MII, - AR8327_PAD_MAC2PHY_GMII, - AR8327_PAD_MAC_RGMII, - AR8327_PAD_PHY_GMII, - AR8327_PAD_PHY_RGMII, - AR8327_PAD_PHY_MII, -}; - -enum ar8327_clk_delay_sel { - AR8327_CLK_DELAY_SEL0 = 0, - AR8327_CLK_DELAY_SEL1, - AR8327_CLK_DELAY_SEL2, - AR8327_CLK_DELAY_SEL3, -}; - -struct ar8327_pad_cfg { - enum ar8327_pad_mode mode; - bool rxclk_sel; - bool txclk_sel; - bool pipe_rxclk_sel; - bool txclk_delay_en; - bool rxclk_delay_en; - bool sgmii_delay_en; - enum ar8327_clk_delay_sel txclk_delay_sel; - enum ar8327_clk_delay_sel rxclk_delay_sel; - bool mac06_exchange_dis; -}; - -enum ar8327_port_speed { - AR8327_PORT_SPEED_10 = 0, - AR8327_PORT_SPEED_100, - AR8327_PORT_SPEED_1000, -}; - -struct ar8327_port_cfg { - int force_link:1; - enum ar8327_port_speed speed; - int txpause:1; - int rxpause:1; - int duplex:1; -}; - -struct ar8327_sgmii_cfg { - u32 sgmii_ctrl; - bool serdes_aen; -}; - -struct ar8327_led_cfg { - u32 led_ctrl0; - u32 led_ctrl1; - u32 led_ctrl2; - u32 led_ctrl3; - bool open_drain; -}; - -enum ar8327_led_num { - AR8327_LED_PHY0_0 = 0, - AR8327_LED_PHY0_1, - AR8327_LED_PHY0_2, - AR8327_LED_PHY1_0, - AR8327_LED_PHY1_1, - AR8327_LED_PHY1_2, - AR8327_LED_PHY2_0, - AR8327_LED_PHY2_1, - AR8327_LED_PHY2_2, - AR8327_LED_PHY3_0, - AR8327_LED_PHY3_1, - AR8327_LED_PHY3_2, - AR8327_LED_PHY4_0, - AR8327_LED_PHY4_1, - AR8327_LED_PHY4_2, -}; - -enum ar8327_led_mode { - AR8327_LED_MODE_HW = 0, - AR8327_LED_MODE_SW, -}; - -struct ar8327_led_info { - const char *name; - const char *default_trigger; - bool active_low; - enum ar8327_led_num led_num; - enum ar8327_led_mode mode; -}; - -#define AR8327_LED_INFO(_led, _mode, _name) { \ - .name = (_name), \ - .led_num = AR8327_LED_ ## _led, \ - .mode = AR8327_LED_MODE_ ## _mode \ -} - -struct ar8327_platform_data { - struct ar8327_pad_cfg *pad0_cfg; - struct ar8327_pad_cfg *pad5_cfg; - struct ar8327_pad_cfg *pad6_cfg; - struct ar8327_sgmii_cfg *sgmii_cfg; - struct ar8327_port_cfg port0_cfg; - struct ar8327_port_cfg port6_cfg; - struct ar8327_led_cfg *led_cfg; - - int (*get_port_link)(unsigned port); - - unsigned num_leds; - const struct ar8327_led_info *leds; -}; - -#endif /* AR8216_PLATFORM_H */ - diff --git a/target/linux/generic/files/include/linux/ath5k_platform.h b/target/linux/generic/files/include/linux/ath5k_platform.h deleted file mode 100644 index ec85224528..0000000000 --- a/target/linux/generic/files/include/linux/ath5k_platform.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2008 Atheros Communications Inc. - * Copyright (c) 2009 Gabor Juhos - * Copyright (c) 2009 Imre Kaloz - * Copyright (c) 2010 Daniel Golle - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _LINUX_ATH5K_PLATFORM_H -#define _LINUX_ATH5K_PLATFORM_H - -#define ATH5K_PLAT_EEP_MAX_WORDS 2048 - -struct ath5k_platform_data { - u16 *eeprom_data; - u8 *macaddr; -}; - -#endif /* _LINUX_ATH5K_PLATFORM_H */ diff --git a/target/linux/generic/files/include/linux/ath9k_platform.h b/target/linux/generic/files/include/linux/ath9k_platform.h deleted file mode 100644 index f1f2ad419c..0000000000 --- a/target/linux/generic/files/include/linux/ath9k_platform.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2008 Atheros Communications Inc. - * Copyright (c) 2009 Gabor Juhos - * Copyright (c) 2009 Imre Kaloz - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef _LINUX_ATH9K_PLATFORM_H -#define _LINUX_ATH9K_PLATFORM_H - -#define ATH9K_PLAT_EEP_MAX_WORDS 2048 - -struct ath9k_platform_data { - const char *eeprom_name; - - u16 eeprom_data[ATH9K_PLAT_EEP_MAX_WORDS]; - u8 *macaddr; - - int led_pin; - u32 gpio_mask; - u32 gpio_val; - - u32 bt_active_pin; - u32 bt_priority_pin; - u32 wlan_active_pin; - - bool endian_check; - bool is_clk_25mhz; - bool tx_gain_buffalo; - bool disable_2ghz; - bool disable_5ghz; - bool led_active_high; - - int (*get_mac_revision)(void); - int (*external_reset)(void); - - bool use_eeprom; - - int num_leds; - const struct gpio_led *leds; - - unsigned num_btns; - const struct gpio_keys_button *btns; - unsigned btn_poll_interval; - - bool ubnt_hsr; -}; - -#endif /* _LINUX_ATH9K_PLATFORM_H */ diff --git a/target/linux/generic/files/include/linux/myloader.h b/target/linux/generic/files/include/linux/myloader.h deleted file mode 100644 index d89e415fba..0000000000 --- a/target/linux/generic/files/include/linux/myloader.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Compex's MyLoader specific definitions - * - * Copyright (C) 2006-2008 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#ifndef _MYLOADER_H_ -#define _MYLOADER_H_ - -/* Myloader specific magic numbers */ -#define MYLO_MAGIC_SYS_PARAMS 0x20021107 -#define MYLO_MAGIC_PARTITIONS 0x20021103 -#define MYLO_MAGIC_BOARD_PARAMS 0x20021103 - -/* Vendor ID's (seems to be same as the PCI vendor ID's) */ -#define VENID_COMPEX 0x11F6 - -/* Devices based on the ADM5120 */ -#define DEVID_COMPEX_NP27G 0x0078 -#define DEVID_COMPEX_NP28G 0x044C -#define DEVID_COMPEX_NP28GHS 0x044E -#define DEVID_COMPEX_WP54Gv1C 0x0514 -#define DEVID_COMPEX_WP54G 0x0515 -#define DEVID_COMPEX_WP54AG 0x0546 -#define DEVID_COMPEX_WPP54AG 0x0550 -#define DEVID_COMPEX_WPP54G 0x0555 - -/* Devices based on the Atheros AR2317 */ -#define DEVID_COMPEX_NP25G 0x05E6 -#define DEVID_COMPEX_WPE53G 0x05DC - -/* Devices based on the Atheros AR71xx */ -#define DEVID_COMPEX_WP543 0x0640 -#define DEVID_COMPEX_WPE72 0x0672 - -/* Devices based on the IXP422 */ -#define DEVID_COMPEX_WP18 0x047E -#define DEVID_COMPEX_NP18A 0x0489 - -/* Other devices */ -#define DEVID_COMPEX_NP26G8M 0x03E8 -#define DEVID_COMPEX_NP26G16M 0x03E9 - -struct mylo_partition { - uint16_t flags; /* partition flags */ - uint16_t type; /* type of the partition */ - uint32_t addr; /* relative address of the partition from the - flash start */ - uint32_t size; /* size of the partition in bytes */ - uint32_t param; /* if this is the active partition, the - MyLoader load code to this address */ -}; - -#define PARTITION_FLAG_ACTIVE 0x8000 /* this is the active partition, - * MyLoader loads firmware from here */ -#define PARTITION_FLAG_ISRAM 0x2000 /* FIXME: this is a RAM partition? */ -#define PARTIIION_FLAG_RAMLOAD 0x1000 /* FIXME: load this partition into the RAM? */ -#define PARTITION_FLAG_PRELOAD 0x0800 /* the partition data preloaded to RAM - * before decompression */ -#define PARTITION_FLAG_LZMA 0x0100 /* partition data compressed by LZMA */ -#define PARTITION_FLAG_HAVEHDR 0x0002 /* the partition data have a header */ - -#define PARTITION_TYPE_FREE 0 -#define PARTITION_TYPE_USED 1 - -#define MYLO_MAX_PARTITIONS 8 /* maximum number of partitions in the - partition table */ - -struct mylo_partition_table { - uint32_t magic; /* must be MYLO_MAGIC_PARTITIONS */ - uint32_t res0; /* unknown/unused */ - uint32_t res1; /* unknown/unused */ - uint32_t res2; /* unknown/unused */ - struct mylo_partition partitions[MYLO_MAX_PARTITIONS]; -}; - -struct mylo_partition_header { - uint32_t len; /* length of the partition data */ - uint32_t crc; /* CRC value of the partition data */ -}; - -struct mylo_system_params { - uint32_t magic; /* must be MYLO_MAGIC_SYS_PARAMS */ - uint32_t res0; - uint32_t res1; - uint32_t mylo_ver; - uint16_t vid; /* Vendor ID */ - uint16_t did; /* Device ID */ - uint16_t svid; /* Sub Vendor ID */ - uint16_t sdid; /* Sub Device ID */ - uint32_t rev; /* device revision */ - uint32_t fwhi; - uint32_t fwlo; - uint32_t tftp_addr; - uint32_t prog_start; - uint32_t flash_size; /* size of boot FLASH in bytes */ - uint32_t dram_size; /* size of onboard RAM in bytes */ -}; - -struct mylo_eth_addr { - uint8_t mac[6]; - uint8_t csum[2]; -}; - -#define MYLO_ETHADDR_COUNT 8 /* maximum number of ethernet address - in the board parameters */ - -struct mylo_board_params { - uint32_t magic; /* must be MYLO_MAGIC_BOARD_PARAMS */ - uint32_t res0; - uint32_t res1; - uint32_t res2; - struct mylo_eth_addr addr[MYLO_ETHADDR_COUNT]; -}; - -#endif /* _MYLOADER_H_*/ diff --git a/target/linux/generic/files/include/linux/platform_data/adm6996-gpio.h b/target/linux/generic/files/include/linux/platform_data/adm6996-gpio.h deleted file mode 100644 index d5af9bbf6e..0000000000 --- a/target/linux/generic/files/include/linux/platform_data/adm6996-gpio.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * ADM6996 GPIO platform data - * - * Copyright (C) 2013 Hauke Mehrtens - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation - */ - -#ifndef __PLATFORM_ADM6996_GPIO_H -#define __PLATFORM_ADM6996_GPIO_H - -#include - -enum adm6996_model { - ADM6996FC = 1, - ADM6996M = 2, - ADM6996L = 3, -}; - -struct adm6996_gpio_platform_data { - u8 eecs; - u8 eesk; - u8 eedi; - enum adm6996_model model; -}; - -#endif diff --git a/target/linux/generic/files/include/linux/platform_data/b53.h b/target/linux/generic/files/include/linux/platform_data/b53.h deleted file mode 100644 index 78427417a1..0000000000 --- a/target/linux/generic/files/include/linux/platform_data/b53.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * B53 platform data - * - * Copyright (C) 2013 Jonas Gorski - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef __B53_H -#define __B53_H - -#include - -struct b53_platform_data { - u32 chip_id; - u16 enabled_ports; - - /* allow to specify an ethX alias */ - const char *alias; - - /* only used by MMAP'd driver */ - unsigned big_endian:1; - void __iomem *regs; -}; - -#endif diff --git a/target/linux/generic/files/include/linux/routerboot.h b/target/linux/generic/files/include/linux/routerboot.h deleted file mode 100644 index 3cda858cf9..0000000000 --- a/target/linux/generic/files/include/linux/routerboot.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Mikrotik's RouterBOOT definitions - * - * Copyright (C) 2007-2008 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#ifndef _ROUTERBOOT_H -#define _ROUTERBOOT_H - -#define RB_MAC_SIZE 6 - -/* - * Magic numbers - */ -#define RB_MAGIC_HARD 0x64726148 /* "Hard" */ -#define RB_MAGIC_SOFT 0x74666F53 /* "Soft" */ -#define RB_MAGIC_DAWN 0x6E776144 /* "Dawn" */ - -#define RB_ID_TERMINATOR 0 - -/* - * ID values for Hardware settings - */ -#define RB_ID_HARD_01 1 -#define RB_ID_HARD_02 2 -#define RB_ID_FLASH_INFO 3 -#define RB_ID_MAC_ADDRESS_PACK 4 -#define RB_ID_BOARD_NAME 5 -#define RB_ID_BIOS_VERSION 6 -#define RB_ID_HARD_07 7 -#define RB_ID_SDRAM_TIMINGS 8 -#define RB_ID_DEVICE_TIMINGS 9 -#define RB_ID_SOFTWARE_ID 10 -#define RB_ID_SERIAL_NUMBER 11 -#define RB_ID_HARD_12 12 -#define RB_ID_MEMORY_SIZE 13 -#define RB_ID_MAC_ADDRESS_COUNT 14 -#define RB_ID_HW_OPTIONS 21 -#define RB_ID_WLAN_DATA 22 - -/* - * ID values for Software settings - */ -#define RB_ID_UART_SPEED 1 -#define RB_ID_BOOT_DELAY 2 -#define RB_ID_BOOT_DEVICE 3 -#define RB_ID_BOOT_KEY 4 -#define RB_ID_CPU_MODE 5 -#define RB_ID_FW_VERSION 6 -#define RB_ID_SOFT_07 7 -#define RB_ID_SOFT_08 8 -#define RB_ID_BOOT_PROTOCOL 9 -#define RB_ID_SOFT_10 10 -#define RB_ID_SOFT_11 11 - -/* - * UART_SPEED values - */ -#define RB_UART_SPEED_115200 0 -#define RB_UART_SPEED_57600 1 -#define RB_UART_SPEED_38400 2 -#define RB_UART_SPEED_19200 3 -#define RB_UART_SPEED_9600 4 -#define RB_UART_SPEED_4800 5 -#define RB_UART_SPEED_2400 6 -#define RB_UART_SPEED_1200 7 - -/* - * BOOT_DELAY values - */ -#define RB_BOOT_DELAY_0SEC 0 -#define RB_BOOT_DELAY_1SEC 1 -#define RB_BOOT_DELAY_2SEC 2 - -/* - * BOOT_DEVICE values - */ -#define RB_BOOT_DEVICE_ETHER 0 -#define RB_BOOT_DEVICE_NANDETH 1 -#define RB_BOOT_DEVICE_ETHONCE 2 -#define RB_BOOT_DEVICE_NANDONLY 3 - -/* - * BOOT_KEY values - */ -#define RB_BOOT_KEY_ANY 0 -#define RB_BOOT_KEY_DEL 1 - -/* - * CPU_MODE values - */ -#define RB_CPU_MODE_POWERSAVE 0 -#define RB_CPU_MODE_REGULAR 1 - -/* - * BOOT_PROTOCOL values - */ -#define RB_BOOT_PROTOCOL_BOOTP 0 -#define RB_BOOT_PROTOCOL_DHCP 1 - -#endif /* _ROUTERBOOT_H */ diff --git a/target/linux/generic/files/include/linux/rt2x00_platform.h b/target/linux/generic/files/include/linux/rt2x00_platform.h deleted file mode 100644 index e10377e21b..0000000000 --- a/target/linux/generic/files/include/linux/rt2x00_platform.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Platform data definition for the rt2x00 driver - * - * Copyright (C) 2011 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - */ - -#ifndef _RT2X00_PLATFORM_H -#define _RT2X00_PLATFORM_H - -struct rt2x00_platform_data { - char *eeprom_file_name; - const u8 *mac_address; - - int disable_2ghz; - int disable_5ghz; -}; - -#endif /* _RT2X00_PLATFORM_H */ diff --git a/target/linux/generic/files/include/linux/rtl8366.h b/target/linux/generic/files/include/linux/rtl8366.h deleted file mode 100644 index e3ce8f5361..0000000000 --- a/target/linux/generic/files/include/linux/rtl8366.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver - * - * Copyright (C) 2009-2010 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#ifndef _RTL8366_H -#define _RTL8366_H - -#define RTL8366_DRIVER_NAME "rtl8366" -#define RTL8366S_DRIVER_NAME "rtl8366s" -#define RTL8366RB_DRIVER_NAME "rtl8366rb" - -struct rtl8366_smi; - -enum rtl8366_type { - RTL8366_TYPE_UNKNOWN, - RTL8366_TYPE_S, - RTL8366_TYPE_RB, -}; - -struct rtl8366_initval { - unsigned reg; - u16 val; -}; - -struct rtl8366_platform_data { - unsigned gpio_sda; - unsigned gpio_sck; - void (*hw_reset)(struct rtl8366_smi *smi, bool active); - - unsigned num_initvals; - struct rtl8366_initval *initvals; -}; - -enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata); - -#endif /* _RTL8366_H */ diff --git a/target/linux/generic/files/include/linux/rtl8367.h b/target/linux/generic/files/include/linux/rtl8367.h deleted file mode 100644 index 855de6a5cc..0000000000 --- a/target/linux/generic/files/include/linux/rtl8367.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Platform data definition for the Realtek RTL8367 ethernet switch driver - * - * Copyright (C) 2011 Gabor Juhos - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - */ - -#ifndef _RTL8367_H -#define _RTL8367_H - -#define RTL8367_DRIVER_NAME "rtl8367" -#define RTL8367B_DRIVER_NAME "rtl8367b" - -enum rtl8367_port_speed { - RTL8367_PORT_SPEED_10 = 0, - RTL8367_PORT_SPEED_100, - RTL8367_PORT_SPEED_1000, -}; - -struct rtl8367_port_ability { - int force_mode; - int nway; - int txpause; - int rxpause; - int link; - int duplex; - enum rtl8367_port_speed speed; -}; - -enum rtl8367_extif_mode { - RTL8367_EXTIF_MODE_DISABLED = 0, - RTL8367_EXTIF_MODE_RGMII, - RTL8367_EXTIF_MODE_MII_MAC, - RTL8367_EXTIF_MODE_MII_PHY, - RTL8367_EXTIF_MODE_TMII_MAC, - RTL8367_EXTIF_MODE_TMII_PHY, - RTL8367_EXTIF_MODE_GMII, - RTL8367_EXTIF_MODE_RGMII_33V, -}; - -struct rtl8367_extif_config { - unsigned int txdelay; - unsigned int rxdelay; - enum rtl8367_extif_mode mode; - struct rtl8367_port_ability ability; -}; - -struct rtl8367_platform_data { - unsigned gpio_sda; - unsigned gpio_sck; - void (*hw_reset)(bool active); - - struct rtl8367_extif_config *extif0_cfg; - struct rtl8367_extif_config *extif1_cfg; -}; - -#endif /* _RTL8367_H */ diff --git a/target/linux/generic/files/include/linux/switch.h b/target/linux/generic/files/include/linux/switch.h deleted file mode 100644 index 4e6238470d..0000000000 --- a/target/linux/generic/files/include/linux/switch.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * switch.h: Switch configuration API - * - * Copyright (C) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ -#ifndef _LINUX_SWITCH_H -#define _LINUX_SWITCH_H - -#include -#include - -struct switch_dev; -struct switch_op; -struct switch_val; -struct switch_attr; -struct switch_attrlist; -struct switch_led_trigger; - -int register_switch(struct switch_dev *dev, struct net_device *netdev); -void unregister_switch(struct switch_dev *dev); - -/** - * struct switch_attrlist - attribute list - * - * @n_attr: number of attributes - * @attr: pointer to the attributes array - */ -struct switch_attrlist { - int n_attr; - const struct switch_attr *attr; -}; - -enum switch_port_speed { - SWITCH_PORT_SPEED_UNKNOWN = 0, - SWITCH_PORT_SPEED_10 = 10, - SWITCH_PORT_SPEED_100 = 100, - SWITCH_PORT_SPEED_1000 = 1000, -}; - -struct switch_port_link { - bool link; - bool duplex; - bool aneg; - bool tx_flow; - bool rx_flow; - enum switch_port_speed speed; - /* in ethtool adv_t format */ - u32 eee; -}; - -struct switch_port_stats { - unsigned long long tx_bytes; - unsigned long long rx_bytes; -}; - -/** - * struct switch_dev_ops - switch driver operations - * - * @attr_global: global switch attribute list - * @attr_port: port attribute list - * @attr_vlan: vlan attribute list - * - * Callbacks: - * - * @get_vlan_ports: read the port list of a VLAN - * @set_vlan_ports: set the port list of a VLAN - * - * @get_port_pvid: get the primary VLAN ID of a port - * @set_port_pvid: set the primary VLAN ID of a port - * - * @apply_config: apply all changed settings to the switch - * @reset_switch: resetting the switch - */ -struct switch_dev_ops { - struct switch_attrlist attr_global, attr_port, attr_vlan; - - int (*get_vlan_ports)(struct switch_dev *dev, struct switch_val *val); - int (*set_vlan_ports)(struct switch_dev *dev, struct switch_val *val); - - int (*get_port_pvid)(struct switch_dev *dev, int port, int *val); - int (*set_port_pvid)(struct switch_dev *dev, int port, int val); - - int (*apply_config)(struct switch_dev *dev); - int (*reset_switch)(struct switch_dev *dev); - - int (*get_port_link)(struct switch_dev *dev, int port, - struct switch_port_link *link); - int (*set_port_link)(struct switch_dev *dev, int port, - struct switch_port_link *link); - int (*get_port_stats)(struct switch_dev *dev, int port, - struct switch_port_stats *stats); - - int (*phy_read16)(struct switch_dev *dev, int addr, u8 reg, u16 *value); - int (*phy_write16)(struct switch_dev *dev, int addr, u8 reg, u16 value); -}; - -struct switch_dev { - struct device_node *of_node; - const struct switch_dev_ops *ops; - /* will be automatically filled */ - char devname[IFNAMSIZ]; - - const char *name; - /* NB: either alias or netdev must be set */ - const char *alias; - struct net_device *netdev; - - unsigned int ports; - unsigned int vlans; - unsigned int cpu_port; - - /* the following fields are internal for swconfig */ - unsigned int id; - struct list_head dev_list; - unsigned long def_global, def_port, def_vlan; - - struct mutex sw_mutex; - struct switch_port *portbuf; - struct switch_portmap *portmap; - struct switch_port_link linkbuf; - - char buf[128]; - -#ifdef CONFIG_SWCONFIG_LEDS - struct switch_led_trigger *led_trigger; -#endif -}; - -struct switch_port { - u32 id; - u32 flags; -}; - -struct switch_portmap { - u32 virt; - const char *s; -}; - -struct switch_val { - const struct switch_attr *attr; - unsigned int port_vlan; - unsigned int len; - union { - const char *s; - u32 i; - struct switch_port *ports; - struct switch_port_link *link; - } value; -}; - -struct switch_attr { - int disabled; - int type; - const char *name; - const char *description; - - int (*set)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); - int (*get)(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val); - - /* for driver internal use */ - int id; - int ofs; - int max; -}; - -int switch_generic_set_link(struct switch_dev *dev, int port, - struct switch_port_link *link); - -#endif /* _LINUX_SWITCH_H */ diff --git a/target/linux/generic/files/include/uapi/linux/switch.h b/target/linux/generic/files/include/uapi/linux/switch.h deleted file mode 100644 index ea449653fa..0000000000 --- a/target/linux/generic/files/include/uapi/linux/switch.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * switch.h: Switch configuration API - * - * Copyright (C) 2008 Felix Fietkau - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - */ - -#ifndef _UAPI_LINUX_SWITCH_H -#define _UAPI_LINUX_SWITCH_H - -#include -#include -#include -#include -#ifndef __KERNEL__ -#include -#include -#include -#endif - -/* main attributes */ -enum { - SWITCH_ATTR_UNSPEC, - /* global */ - SWITCH_ATTR_TYPE, - /* device */ - SWITCH_ATTR_ID, - SWITCH_ATTR_DEV_NAME, - SWITCH_ATTR_ALIAS, - SWITCH_ATTR_NAME, - SWITCH_ATTR_VLANS, - SWITCH_ATTR_PORTS, - SWITCH_ATTR_PORTMAP, - SWITCH_ATTR_CPU_PORT, - /* attributes */ - SWITCH_ATTR_OP_ID, - SWITCH_ATTR_OP_TYPE, - SWITCH_ATTR_OP_NAME, - SWITCH_ATTR_OP_PORT, - SWITCH_ATTR_OP_VLAN, - SWITCH_ATTR_OP_VALUE_INT, - SWITCH_ATTR_OP_VALUE_STR, - SWITCH_ATTR_OP_VALUE_PORTS, - SWITCH_ATTR_OP_VALUE_LINK, - SWITCH_ATTR_OP_DESCRIPTION, - /* port lists */ - SWITCH_ATTR_PORT, - SWITCH_ATTR_MAX -}; - -enum { - /* port map */ - SWITCH_PORTMAP_PORTS, - SWITCH_PORTMAP_SEGMENT, - SWITCH_PORTMAP_VIRT, - SWITCH_PORTMAP_MAX -}; - -/* commands */ -enum { - SWITCH_CMD_UNSPEC, - SWITCH_CMD_GET_SWITCH, - SWITCH_CMD_NEW_ATTR, - SWITCH_CMD_LIST_GLOBAL, - SWITCH_CMD_GET_GLOBAL, - SWITCH_CMD_SET_GLOBAL, - SWITCH_CMD_LIST_PORT, - SWITCH_CMD_GET_PORT, - SWITCH_CMD_SET_PORT, - SWITCH_CMD_LIST_VLAN, - SWITCH_CMD_GET_VLAN, - SWITCH_CMD_SET_VLAN -}; - -/* data types */ -enum switch_val_type { - SWITCH_TYPE_UNSPEC, - SWITCH_TYPE_INT, - SWITCH_TYPE_STRING, - SWITCH_TYPE_PORTS, - SWITCH_TYPE_LINK, - SWITCH_TYPE_NOVAL, -}; - -/* port nested attributes */ -enum { - SWITCH_PORT_UNSPEC, - SWITCH_PORT_ID, - SWITCH_PORT_FLAG_TAGGED, - SWITCH_PORT_ATTR_MAX -}; - -/* link nested attributes */ -enum { - SWITCH_LINK_UNSPEC, - SWITCH_LINK_FLAG_LINK, - SWITCH_LINK_FLAG_DUPLEX, - SWITCH_LINK_FLAG_ANEG, - SWITCH_LINK_FLAG_TX_FLOW, - SWITCH_LINK_FLAG_RX_FLOW, - SWITCH_LINK_SPEED, - SWITCH_LINK_FLAG_EEE_100BASET, - SWITCH_LINK_FLAG_EEE_1000BASET, - SWITCH_LINK_ATTR_MAX, -}; - -#define SWITCH_ATTR_DEFAULTS_OFFSET 0x1000 - - -#endif /* _UAPI_LINUX_SWITCH_H */