--- /dev/null
+-------
+
+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.
--- /dev/null
+#
+# Makefile for the Compex's MyLoader support on MIPS architecture
+#
+
+lib-y += myloader.o
--- /dev/null
+/*
+ * Compex's MyLoader specific prom routines
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+
+#include <asm/addrspace.h>
+#include <asm/fw/myloader/myloader.h>
+
+#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;
+}
--- /dev/null
+/*
+ * LED Kernel Netdev Trigger
+ *
+ * Toggles the LED to reflect the link and traffic state of a named net device
+ *
+ * Copyright 2007 Oliver Jowett <oliver@opencloud.com>
+ *
+ * Derived from ledtrig-timer.c which is:
+ * Copyright 2005-2006 Openedhand Ltd.
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+
+#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 <oliver@opencloud.com>");
+MODULE_DESCRIPTION("Netdev LED trigger");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Initialize Owl Emulation Devices
+ *
+ * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/version.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/ath9k_platform.h>
+
+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 <chunkeey@googlemail.com>");
+MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
+
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (c) 2015 The Linux Foundation
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <linux/slab.h>
+#include <linux/of_fdt.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
+ *
+ * Based on: mtdsplit_uimage.c
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/unaligned.h>
+
+#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);
--- /dev/null
+/*
+ * MTD splitter for MikroTik NOR devices
+ *
+ * Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/string.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Parse MyLoader-style flash partition tables and produce a Linux partition
+ * array to match.
+ *
+ * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This file was based on drivers/mtd/redboot.c
+ * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/myloader.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * swconfig interface based on ar8216.c
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
+ * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/adm6996-gpio.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/switch.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "adm6996.h"
+
+MODULE_DESCRIPTION("Infineon ADM6996 Switch");
+MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
+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);
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8216.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+#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");
--- /dev/null
+/*
+ * ar8216.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8327.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+#include <linux/leds.h>
+#include <linux/mdio.h>
+
+#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
+};
--- /dev/null
+/*
+ * ar8327.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * B53 switch driver main logic
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 switch library");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through MII registers
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+
+#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");
--- /dev/null
+/*
+ * B53 register access through memory mapped registers
+ *
+ * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 MMAP access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 PHY Fixup call
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/phy.h>
+
+#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);
--- /dev/null
+/*
+ * B53 common definitions
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/switch.h>
+
+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 <bcm47xx_board.h>
+#endif
+
+#include <linux/version.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+#include <linux/bcm47xx_nvram.h>
+#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
--- /dev/null
+/*
+ * B53 register definitions
+ *
+ * Copyright (C) 2004 Broadcom Corporation
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * B53 register access through SPI
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <asm/unaligned.h>
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 SPI access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through Switch Register Access Bridge Registers
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <hauke@hauke-m.de>");
+MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family
+ *
+ * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com>
+ * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+
+#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); \
+ } \
+ } while (0)
+
+#define SET_PORT_BITS(state, bits, addr, bit_lookup) \
+ do { \
+ int i, val = getPhy((state), (addr)); \
+ if (val < 0) \
+ return val; \
+ for (i = 0; i < MAX_PORTS; i++) { \
+ unsigned int newmask = ((bits)&(1<<i)); \
+ if ((bit_lookup)[i] == -1) continue; \
+ val &= ~(1<<(bit_lookup)[i]); \
+ val |= ((newmask>>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<<state->regs->ROUTER_EN_BIT);
+ } else {
+ val &= (~(1<<state->regs->ROUTER_EN_BIT));
+ }
+ }
+ if (state->regs->TAG_VLAN_BIT >= 0) {
+ if (state->vlan_enabled) {
+ val |= (1<<state->regs->TAG_VLAN_BIT);
+ } else {
+ val &= (~(1<<state->regs->TAG_VLAN_BIT));
+ }
+ }
+ if (state->regs->NUMLAN_GROUPS_BIT >= 0) {
+ val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->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; j<state->regs->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; j<MAX_VLANS; j++) {
+ reg addr = state->regs->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; i<MAX_PORTS; i++) {
+ if (REG_SUPP(state->regs->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; i<MAX_VLANS; i++) {
+ if (state->vlans[i].ports != 0) {
+ state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere...
+ }
+ }
+
+ for (i=0; i<state->regs->NUM_PORTS; i++) {
+ unsigned int portmask = (1<<i);
+ if (!state->vlan_enabled) {
+ // Share with everybody!
+ state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1;
+ continue;
+ }
+ state->ports[i].shareports = portmask;
+ for (j=0; j<MAX_VLANS; j++) {
+ if (state->vlans[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<<state->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<<val->value.ports[i].id);
+ state->vlans[val->port_vlan].ports |= bitmask;
+ if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) {
+ state->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<<state->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<<val->port_vlan)) {
+ if (state->remove_tag & (1<<val->port_vlan))
+ val->value.i = 3; // shouldn't ever happen.
+ else
+ val->value.i = 1;
+ } else {
+ if (state->remove_tag & (1<<val->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<<val->port_vlan);
+ state->remove_tag &= ~(1<<val->port_vlan);
+
+ if (val->value.i == 0)
+ state->remove_tag |= (1<<val->port_vlan);
+ if (val->value.i == 1)
+ state->add_tag |= (1<<val->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 <patrick.horn@gmail.com>");
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+MODULE_AUTHOR("Martin Mares <mj@ucw.cz>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include "mvsw61xx.h"
+
+MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
+MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
+MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
+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);
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/if_vlan.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#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);
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+
+#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 <ithamar.adema@team-embedded.nl>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * rtl8306.c: RTL8306S switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/version.h>
+
+//#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");
+
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/rtl8366.h>
+#include <linux/version.h>
+#include <linux/of_mdio.h>
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+#include <linux/debugfs.h>
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver defines
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/phy.h>
+#include <linux/switch.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+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 */
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366RB ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
+ * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>");
+MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366S ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R/M ethernet switches
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R-VB ethernet switches
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME);
+
--- /dev/null
+/*
+ * swconfig.c: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/capability.h>
+#include <linux/skbuff.h>
+#include <linux/switch.h>
+#include <linux/of.h>
+#include <linux/version.h>
+#include <uapi/linux/mii.h>
+
+#define SWCONFIG_DEVNAME "switch%d"
+
+#include "swconfig_leds.c"
+
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+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);
--- /dev/null
+/*
+ * swconfig_led.c: LED trigger support for the switch configuration API
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/leds.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#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 */
--- /dev/null
+/*
+ * AR8216 switch driver platform data
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
+
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ * Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Compex's MyLoader specific definitions
+ *
+ * Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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_*/
--- /dev/null
+/*
+ * ADM6996 GPIO platform data
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * B53 platform data
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * Mikrotik's RouterBOOT definitions
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the rt2x00 driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8367 ethernet switch driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <net/genetlink.h>
+#include <uapi/linux/switch.h>
+
+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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#ifndef __KERNEL__
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#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 */
--- /dev/null
+-------
+
+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.
--- /dev/null
+#
+# Makefile for the Compex's MyLoader support on MIPS architecture
+#
+
+lib-y += myloader.o
--- /dev/null
+/*
+ * Compex's MyLoader specific prom routines
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+
+#include <asm/addrspace.h>
+#include <asm/fw/myloader/myloader.h>
+
+#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;
+}
--- /dev/null
+/*
+ * LED Kernel Netdev Trigger
+ *
+ * Toggles the LED to reflect the link and traffic state of a named net device
+ *
+ * Copyright 2007 Oliver Jowett <oliver@opencloud.com>
+ *
+ * Derived from ledtrig-timer.c which is:
+ * Copyright 2005-2006 Openedhand Ltd.
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+
+#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 <oliver@opencloud.com>");
+MODULE_DESCRIPTION("Netdev LED trigger");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Initialize Owl Emulation Devices
+ *
+ * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/version.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/ath9k_platform.h>
+
+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 <chunkeey@googlemail.com>");
+MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
+
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (c) 2015 The Linux Foundation
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <linux/slab.h>
+#include <linux/of_fdt.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
+ *
+ * Based on: mtdsplit_uimage.c
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/unaligned.h>
+
+#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);
--- /dev/null
+/*
+ * MTD splitter for MikroTik NOR devices
+ *
+ * Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/string.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Parse MyLoader-style flash partition tables and produce a Linux partition
+ * array to match.
+ *
+ * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This file was based on drivers/mtd/redboot.c
+ * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/myloader.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * swconfig interface based on ar8216.c
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
+ * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/adm6996-gpio.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/switch.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "adm6996.h"
+
+MODULE_DESCRIPTION("Infineon ADM6996 Switch");
+MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
+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);
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8216.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+#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");
--- /dev/null
+/*
+ * ar8216.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8327.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+#include <linux/leds.h>
+#include <linux/mdio.h>
+
+#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
+};
--- /dev/null
+/*
+ * ar8327.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * B53 switch driver main logic
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 switch library");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through MII registers
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+
+#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");
--- /dev/null
+/*
+ * B53 register access through memory mapped registers
+ *
+ * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 MMAP access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 PHY Fixup call
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/phy.h>
+
+#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);
--- /dev/null
+/*
+ * B53 common definitions
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/switch.h>
+
+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 <bcm47xx_board.h>
+#endif
+
+#include <linux/version.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+#include <linux/bcm47xx_nvram.h>
+#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
--- /dev/null
+/*
+ * B53 register definitions
+ *
+ * Copyright (C) 2004 Broadcom Corporation
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * B53 register access through SPI
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <asm/unaligned.h>
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 SPI access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through Switch Register Access Bridge Registers
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <hauke@hauke-m.de>");
+MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family
+ *
+ * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com>
+ * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+
+#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); \
+ } \
+ } while (0)
+
+#define SET_PORT_BITS(state, bits, addr, bit_lookup) \
+ do { \
+ int i, val = getPhy((state), (addr)); \
+ if (val < 0) \
+ return val; \
+ for (i = 0; i < MAX_PORTS; i++) { \
+ unsigned int newmask = ((bits)&(1<<i)); \
+ if ((bit_lookup)[i] == -1) continue; \
+ val &= ~(1<<(bit_lookup)[i]); \
+ val |= ((newmask>>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<<state->regs->ROUTER_EN_BIT);
+ } else {
+ val &= (~(1<<state->regs->ROUTER_EN_BIT));
+ }
+ }
+ if (state->regs->TAG_VLAN_BIT >= 0) {
+ if (state->vlan_enabled) {
+ val |= (1<<state->regs->TAG_VLAN_BIT);
+ } else {
+ val &= (~(1<<state->regs->TAG_VLAN_BIT));
+ }
+ }
+ if (state->regs->NUMLAN_GROUPS_BIT >= 0) {
+ val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->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; j<state->regs->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; j<MAX_VLANS; j++) {
+ reg addr = state->regs->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; i<MAX_PORTS; i++) {
+ if (REG_SUPP(state->regs->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; i<MAX_VLANS; i++) {
+ if (state->vlans[i].ports != 0) {
+ state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere...
+ }
+ }
+
+ for (i=0; i<state->regs->NUM_PORTS; i++) {
+ unsigned int portmask = (1<<i);
+ if (!state->vlan_enabled) {
+ // Share with everybody!
+ state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1;
+ continue;
+ }
+ state->ports[i].shareports = portmask;
+ for (j=0; j<MAX_VLANS; j++) {
+ if (state->vlans[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<<state->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<<val->value.ports[i].id);
+ state->vlans[val->port_vlan].ports |= bitmask;
+ if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) {
+ state->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<<state->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<<val->port_vlan)) {
+ if (state->remove_tag & (1<<val->port_vlan))
+ val->value.i = 3; // shouldn't ever happen.
+ else
+ val->value.i = 1;
+ } else {
+ if (state->remove_tag & (1<<val->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<<val->port_vlan);
+ state->remove_tag &= ~(1<<val->port_vlan);
+
+ if (val->value.i == 0)
+ state->remove_tag |= (1<<val->port_vlan);
+ if (val->value.i == 1)
+ state->add_tag |= (1<<val->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 <patrick.horn@gmail.com>");
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+MODULE_AUTHOR("Martin Mares <mj@ucw.cz>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include "mvsw61xx.h"
+
+MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
+MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
+MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
+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);
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/if_vlan.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#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);
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+
+#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 <ithamar.adema@team-embedded.nl>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * rtl8306.c: RTL8306S switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/version.h>
+
+//#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");
+
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/rtl8366.h>
+#include <linux/version.h>
+#include <linux/of_mdio.h>
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+#include <linux/debugfs.h>
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver defines
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/phy.h>
+#include <linux/switch.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+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 */
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366RB ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
+ * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>");
+MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366S ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R/M ethernet switches
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R-VB ethernet switches
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME);
+
--- /dev/null
+/*
+ * swconfig.c: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/capability.h>
+#include <linux/skbuff.h>
+#include <linux/switch.h>
+#include <linux/of.h>
+#include <linux/version.h>
+#include <uapi/linux/mii.h>
+
+#define SWCONFIG_DEVNAME "switch%d"
+
+#include "swconfig_leds.c"
+
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+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);
--- /dev/null
+/*
+ * swconfig_led.c: LED trigger support for the switch configuration API
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/leds.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#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 */
--- /dev/null
+/*
+ * AR8216 switch driver platform data
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
+
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ * Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Compex's MyLoader specific definitions
+ *
+ * Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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_*/
--- /dev/null
+/*
+ * ADM6996 GPIO platform data
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * B53 platform data
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * Mikrotik's RouterBOOT definitions
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the rt2x00 driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8367 ethernet switch driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <net/genetlink.h>
+#include <uapi/linux/switch.h>
+
+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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#ifndef __KERNEL__
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#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 */
--- /dev/null
+-------
+
+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.
--- /dev/null
+#
+# Makefile for the Compex's MyLoader support on MIPS architecture
+#
+
+lib-y += myloader.o
--- /dev/null
+/*
+ * Compex's MyLoader specific prom routines
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+
+#include <asm/addrspace.h>
+#include <asm/fw/myloader/myloader.h>
+
+#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;
+}
--- /dev/null
+/*
+ * LED Kernel Netdev Trigger
+ *
+ * Toggles the LED to reflect the link and traffic state of a named net device
+ *
+ * Copyright 2007 Oliver Jowett <oliver@opencloud.com>
+ *
+ * Derived from ledtrig-timer.c which is:
+ * Copyright 2005-2006 Openedhand Ltd.
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/device.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+
+#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 <oliver@opencloud.com>");
+MODULE_DESCRIPTION("Netdev LED trigger");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Initialize Owl Emulation Devices
+ *
+ * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
+ * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/version.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/ath9k_platform.h>
+
+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 <chunkeey@googlemail.com>");
+MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
+
--- /dev/null
+/*
+ * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (c) 2015 The Linux Foundation
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/types.h>
+#include <linux/byteorder/generic.h>
+#include <linux/slab.h>
+#include <linux/of_fdt.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
+ *
+ * Based on: mtdsplit_uimage.c
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/unaligned.h>
+
+#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);
--- /dev/null
+/*
+ * MTD splitter for MikroTik NOR devices
+ *
+ * Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/string.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/magic.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
+ *
+ * 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 <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+
+#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);
--- /dev/null
+/*
+ * Parse MyLoader-style flash partition tables and produce a Linux partition
+ * array to match.
+ *
+ * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * This file was based on drivers/mtd/redboot.c
+ * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/byteorder/generic.h>
+#include <linux/myloader.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * swconfig interface based on ar8216.c
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
+ * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/adm6996-gpio.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/switch.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include "adm6996.h"
+
+MODULE_DESCRIPTION("Infineon ADM6996 Switch");
+MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
+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);
--- /dev/null
+/*
+ * ADM6996 switch driver
+ *
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8216.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/bitops.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+#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");
--- /dev/null
+/*
+ * ar8216.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * ar8327.c: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/list.h>
+#include <linux/bitops.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/lockdep.h>
+#include <linux/ar8216_platform.h>
+#include <linux/workqueue.h>
+#include <linux/of_device.h>
+#include <linux/leds.h>
+#include <linux/mdio.h>
+
+#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
+};
--- /dev/null
+/*
+ * ar8327.h: AR8216 switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * B53 switch driver main logic
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_net.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 switch library");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through MII registers
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/phy.h>
+#include <linux/module.h>
+
+#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");
--- /dev/null
+/*
+ * B53 register access through memory mapped registers
+ *
+ * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 MMAP access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 PHY Fixup call
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/phy.h>
+
+#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);
--- /dev/null
+/*
+ * B53 common definitions
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/switch.h>
+
+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 <bcm47xx_board.h>
+#endif
+
+#include <linux/version.h>
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
+#include <linux/bcm47xx_nvram.h>
+#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
--- /dev/null
+/*
+ * B53 register definitions
+ *
+ * Copyright (C) 2004 Broadcom Corporation
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * B53 register access through SPI
+ *
+ * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <asm/unaligned.h>
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+#include <linux/platform_data/b53.h>
+
+#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 <jogo@openwrt.org>");
+MODULE_DESCRIPTION("B53 SPI access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * B53 register access through Switch Register Access Bridge Registers
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/b53.h>
+
+#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 <hauke@hauke-m.de>");
+MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
+MODULE_LICENSE("Dual BSD/GPL");
--- /dev/null
+/*
+ * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family
+ *
+ * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com>
+ * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/skbuff.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+
+#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); \
+ } \
+ } while (0)
+
+#define SET_PORT_BITS(state, bits, addr, bit_lookup) \
+ do { \
+ int i, val = getPhy((state), (addr)); \
+ if (val < 0) \
+ return val; \
+ for (i = 0; i < MAX_PORTS; i++) { \
+ unsigned int newmask = ((bits)&(1<<i)); \
+ if ((bit_lookup)[i] == -1) continue; \
+ val &= ~(1<<(bit_lookup)[i]); \
+ val |= ((newmask>>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<<state->regs->ROUTER_EN_BIT);
+ } else {
+ val &= (~(1<<state->regs->ROUTER_EN_BIT));
+ }
+ }
+ if (state->regs->TAG_VLAN_BIT >= 0) {
+ if (state->vlan_enabled) {
+ val |= (1<<state->regs->TAG_VLAN_BIT);
+ } else {
+ val &= (~(1<<state->regs->TAG_VLAN_BIT));
+ }
+ }
+ if (state->regs->NUMLAN_GROUPS_BIT >= 0) {
+ val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->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; j<state->regs->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; j<MAX_VLANS; j++) {
+ reg addr = state->regs->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; i<MAX_PORTS; i++) {
+ if (REG_SUPP(state->regs->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; i<MAX_VLANS; i++) {
+ if (state->vlans[i].ports != 0) {
+ state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere...
+ }
+ }
+
+ for (i=0; i<state->regs->NUM_PORTS; i++) {
+ unsigned int portmask = (1<<i);
+ if (!state->vlan_enabled) {
+ // Share with everybody!
+ state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1;
+ continue;
+ }
+ state->ports[i].shareports = portmask;
+ for (j=0; j<MAX_VLANS; j++) {
+ if (state->vlans[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<<state->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<<val->value.ports[i].id);
+ state->vlans[val->port_vlan].ports |= bitmask;
+ if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) {
+ state->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<<state->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<<val->port_vlan)) {
+ if (state->remove_tag & (1<<val->port_vlan))
+ val->value.i = 3; // shouldn't ever happen.
+ else
+ val->value.i = 1;
+ } else {
+ if (state->remove_tag & (1<<val->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<<val->port_vlan);
+ state->remove_tag &= ~(1<<val->port_vlan);
+
+ if (val->value.i == 0)
+ state->remove_tag |= (1<<val->port_vlan);
+ if (val->value.i == 1)
+ state->add_tag |= (1<<val->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 <patrick.horn@gmail.com>");
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+MODULE_AUTHOR("Martin Mares <mj@ucw.cz>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/delay.h>
+#include <linux/switch.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+
+#include "mvsw61xx.h"
+
+MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
+MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
+MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
+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);
--- /dev/null
+/*
+ * Marvell 88E61xx switch driver
+ *
+ * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
+ *
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/ethtool.h>
+#include <linux/phy.h>
+#include <linux/if_vlan.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#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);
--- /dev/null
+/*
+ * Marvell 88E6060 switch driver
+ * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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
--- /dev/null
+/*
+ * 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 <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/switch.h>
+#include <linux/phy.h>
+
+#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 <ithamar.adema@team-embedded.nl>");
+MODULE_LICENSE("GPL");
--- /dev/null
+/*
+ * rtl8306.c: RTL8306S switch driver
+ *
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/if.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <net/genetlink.h>
+#include <linux/switch.h>
+#include <linux/delay.h>
+#include <linux/phy.h>
+#include <linux/version.h>
+
+//#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");
+
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
+#include <linux/rtl8366.h>
+#include <linux/version.h>
+#include <linux/of_mdio.h>
+
+#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
+#include <linux/debugfs.h>
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
--- /dev/null
+/*
+ * Realtek RTL8366 SMI interface driver defines
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/phy.h>
+#include <linux/switch.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+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 */
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366RB ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
+ * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>");
+MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8366S ethernet switch
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8366.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R/M ethernet switches
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME);
--- /dev/null
+/*
+ * Platform driver for the Realtek RTL8367R-VB ethernet switches
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+#include <linux/rtl8367.h>
+
+#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 <juhosg@openwrt.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME);
+
--- /dev/null
+/*
+ * swconfig.c: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/capability.h>
+#include <linux/skbuff.h>
+#include <linux/switch.h>
+#include <linux/of.h>
+#include <linux/version.h>
+#include <uapi/linux/mii.h>
+
+#define SWCONFIG_DEVNAME "switch%d"
+
+#include "swconfig_leds.c"
+
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
+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);
--- /dev/null
+/*
+ * swconfig_led.c: LED trigger support for the switch configuration API
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 <linux/leds.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+
+#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 */
--- /dev/null
+/*
+ * AR8216 switch driver platform data
+ *
+ * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
+
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ * Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Copyright (c) 2008 Atheros Communications Inc.
+ * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
+ * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Compex's MyLoader specific definitions
+ *
+ * Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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_*/
--- /dev/null
+/*
+ * ADM6996 GPIO platform data
+ *
+ * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * B53 platform data
+ *
+ * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
+ *
+ * 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 <linux/kernel.h>
+
+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
--- /dev/null
+/*
+ * Mikrotik's RouterBOOT definitions
+ *
+ * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the rt2x00 driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
+ *
+ * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * Platform data definition for the Realtek RTL8367 ethernet switch driver
+ *
+ * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
+ *
+ * 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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <net/genetlink.h>
+#include <uapi/linux/switch.h>
+
+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 */
--- /dev/null
+/*
+ * switch.h: Switch configuration API
+ *
+ * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
+ *
+ * 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 <linux/types.h>
+#include <linux/netdevice.h>
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#ifndef __KERNEL__
+#include <netlink/netlink.h>
+#include <netlink/genl/genl.h>
+#include <netlink/genl/ctrl.h>
+#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 */
+++ /dev/null
--------
-
-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.
+++ /dev/null
-#
-# Makefile for the Compex's MyLoader support on MIPS architecture
-#
-
-lib-y += myloader.o
+++ /dev/null
-/*
- * Compex's MyLoader specific prom routines
- *
- * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/types.h>
-#include <linux/string.h>
-
-#include <asm/addrspace.h>
-#include <asm/fw/myloader/myloader.h>
-
-#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;
-}
+++ /dev/null
-/*
- * LED Kernel Netdev Trigger
- *
- * Toggles the LED to reflect the link and traffic state of a named net device
- *
- * Copyright 2007 Oliver Jowett <oliver@opencloud.com>
- *
- * Derived from ledtrig-timer.c which is:
- * Copyright 2005-2006 Openedhand Ltd.
- * Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * 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 <linux/module.h>
-#include <linux/jiffies.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/spinlock.h>
-#include <linux/device.h>
-#include <linux/netdevice.h>
-#include <linux/timer.h>
-#include <linux/ctype.h>
-#include <linux/leds.h>
-
-#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 <oliver@opencloud.com>");
-MODULE_DESCRIPTION("Netdev LED trigger");
-MODULE_LICENSE("GPL");
+++ /dev/null
-/*
- * Initialize Owl Emulation Devices
- *
- * Copyright (C) 2016 Christian Lamparter <chunkeey@googlemail.com>
- * Copyright (C) 2016 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
- *
- * 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 <linux/module.h>
-#include <linux/version.h>
-#include <linux/completion.h>
-#include <linux/etherdevice.h>
-#include <linux/firmware.h>
-#include <linux/pci.h>
-#include <linux/delay.h>
-#include <linux/platform_device.h>
-#include <linux/ath9k_platform.h>
-
-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 <chunkeey@googlemail.com>");
-MODULE_DESCRIPTION("Initializes Atheros' Owl Emulation devices");
-MODULE_LICENSE("GPL v2");
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-/*
- * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
- * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
- *
- * 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 <linux/export.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/magic.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
-
+++ /dev/null
-/*
- * Copyright (C) 2009-2013 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2009-2013 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2012 Jonas Gorski <jogo@openwrt.org>
- * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
- *
- * 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 */
+++ /dev/null
-/*
- * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
- * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2012 John Crispin <blogic@openwrt.org>
- * Copyright (C) 2015 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (c) 2015 The Linux Foundation
- * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/types.h>
-#include <linux/byteorder/generic.h>
-#include <linux/slab.h>
-#include <linux/of_fdt.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2018 Paweł Dembicki <paweldembicki@gmail.com>
- *
- * Based on: mtdsplit_uimage.c
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/vmalloc.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2014 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-
-#include <asm/unaligned.h>
-
-#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);
+++ /dev/null
-/*
- * MTD splitter for MikroTik NOR devices
- *
- * Copyright (C) 2017 Thibaut VARENE <varenet@parisc-linux.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/string.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/magic.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/vmalloc.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Copyright (C) 2013 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2014 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2016 Stijn Tintel <stijn@linux-ipv6.be>
- *
- * 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 <linux/module.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-
-#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);
+++ /dev/null
-/*
- * Parse MyLoader-style flash partition tables and produce a Linux partition
- * array to match.
- *
- * Copyright (C) 2007-2009 Gabor Juhos <juhosg@openwrt.org>
- *
- * This file was based on drivers/mtd/redboot.c
- * Author: Red Hat, Inc. - David Woodhouse <dwmw2@cambridge.redhat.com>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/version.h>
-#include <linux/slab.h>
-#include <linux/init.h>
-#include <linux/vmalloc.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/byteorder/generic.h>
-#include <linux/myloader.h>
-
-#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 <juhosg@openwrt.org>");
-MODULE_DESCRIPTION("Parsing code for MyLoader partition tables");
-MODULE_LICENSE("GPL v2");
+++ /dev/null
-/*
- * ADM6996 switch driver
- *
- * swconfig interface based on ar8216.c
- *
- * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
- * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
- * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
- * Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
- *
- * 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 <linux/kernel.h>
-#include <linux/string.h>
-#include <linux/errno.h>
-#include <linux/unistd.h>
-#include <linux/slab.h>
-#include <linux/interrupt.h>
-#include <linux/init.h>
-#include <linux/delay.h>
-#include <linux/gpio.h>
-#include <linux/netdevice.h>
-#include <linux/etherdevice.h>
-#include <linux/skbuff.h>
-#include <linux/spinlock.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/mii.h>
-#include <linux/platform_device.h>
-#include <linux/platform_data/adm6996-gpio.h>
-#include <linux/ethtool.h>
-#include <linux/phy.h>
-#include <linux/switch.h>
-
-#include <asm/io.h>
-#include <asm/irq.h>
-#include <asm/uaccess.h>
-#include "adm6996.h"
-
-MODULE_DESCRIPTION("Infineon ADM6996 Switch");
-MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <peter@digitalbrains.com>");
-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);
+++ /dev/null
-/*
- * ADM6996 switch driver
- *
- * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
- * Copyright (c) 2010,2011 Peter Lebbing <peter@digitalbrains.com>
- *
- * 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
+++ /dev/null
-/*
- * ar8216.c: AR8216 switch driver
- *
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/if.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/if_ether.h>
-#include <linux/skbuff.h>
-#include <linux/netdevice.h>
-#include <linux/netlink.h>
-#include <linux/bitops.h>
-#include <net/genetlink.h>
-#include <linux/switch.h>
-#include <linux/delay.h>
-#include <linux/phy.h>
-#include <linux/netdevice.h>
-#include <linux/etherdevice.h>
-#include <linux/lockdep.h>
-#include <linux/ar8216_platform.h>
-#include <linux/workqueue.h>
-#include <linux/version.h>
-
-#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");
+++ /dev/null
-/*
- * ar8216.h: AR8216 switch driver
- *
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- *
- * 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
+++ /dev/null
-/*
- * ar8327.c: AR8216 switch driver
- *
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/list.h>
-#include <linux/bitops.h>
-#include <linux/switch.h>
-#include <linux/delay.h>
-#include <linux/phy.h>
-#include <linux/lockdep.h>
-#include <linux/ar8216_platform.h>
-#include <linux/workqueue.h>
-#include <linux/of_device.h>
-#include <linux/leds.h>
-#include <linux/mdio.h>
-
-#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
-};
+++ /dev/null
-/*
- * ar8327.h: AR8216 switch driver
- *
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- *
- * 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
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-/*
- * B53 switch driver main logic
- *
- * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/delay.h>
-#include <linux/export.h>
-#include <linux/gpio.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/switch.h>
-#include <linux/phy.h>
-#include <linux/of.h>
-#include <linux/of_net.h>
-#include <linux/platform_data/b53.h>
-
-#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 <jogo@openwrt.org>");
-MODULE_DESCRIPTION("B53 switch library");
-MODULE_LICENSE("Dual BSD/GPL");
+++ /dev/null
-/*
- * B53 register access through MII registers
- *
- * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/phy.h>
-#include <linux/module.h>
-
-#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");
+++ /dev/null
-/*
- * B53 register access through memory mapped registers
- *
- * Copyright (C) 2012-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/platform_data/b53.h>
-
-#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 <jogo@openwrt.org>");
-MODULE_DESCRIPTION("B53 MMAP access driver");
-MODULE_LICENSE("Dual BSD/GPL");
+++ /dev/null
-/*
- * B53 PHY Fixup call
- *
- * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/phy.h>
-
-#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);
+++ /dev/null
-/*
- * B53 common definitions
- *
- * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/mutex.h>
-#include <linux/switch.h>
-
-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 <bcm47xx_board.h>
-#endif
-
-#include <linux/version.h>
-#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0))
-#include <linux/bcm47xx_nvram.h>
-#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
+++ /dev/null
-/*
- * B53 register definitions
- *
- * Copyright (C) 2004 Broadcom Corporation
- * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * B53 register access through SPI
- *
- * Copyright (C) 2011-2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <asm/unaligned.h>
-
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/spi/spi.h>
-#include <linux/of.h>
-#include <linux/platform_data/b53.h>
-
-#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 <jogo@openwrt.org>");
-MODULE_DESCRIPTION("B53 SPI access driver");
-MODULE_LICENSE("Dual BSD/GPL");
+++ /dev/null
-/*
- * B53 register access through Switch Register Access Bridge Registers
- *
- * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/platform_data/b53.h>
-
-#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 <hauke@hauke-m.de>");
-MODULE_DESCRIPTION("B53 Switch Register Access Bridge Registers (SRAB) access driver");
-MODULE_LICENSE("Dual BSD/GPL");
+++ /dev/null
-/*
- * ip17xx.c: Swconfig configuration for IC+ IP17xx switch family
- *
- * Copyright (C) 2008 Patrick Horn <patrick.horn@gmail.com>
- * Copyright (C) 2008, 2010 Martin Mares <mj@ucw.cz>
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/skbuff.h>
-#include <linux/mii.h>
-#include <linux/phy.h>
-#include <linux/delay.h>
-#include <linux/switch.h>
-#include <linux/device.h>
-
-#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); \
- } \
- } while (0)
-
-#define SET_PORT_BITS(state, bits, addr, bit_lookup) \
- do { \
- int i, val = getPhy((state), (addr)); \
- if (val < 0) \
- return val; \
- for (i = 0; i < MAX_PORTS; i++) { \
- unsigned int newmask = ((bits)&(1<<i)); \
- if ((bit_lookup)[i] == -1) continue; \
- val &= ~(1<<(bit_lookup)[i]); \
- val |= ((newmask>>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<<state->regs->ROUTER_EN_BIT);
- } else {
- val &= (~(1<<state->regs->ROUTER_EN_BIT));
- }
- }
- if (state->regs->TAG_VLAN_BIT >= 0) {
- if (state->vlan_enabled) {
- val |= (1<<state->regs->TAG_VLAN_BIT);
- } else {
- val &= (~(1<<state->regs->TAG_VLAN_BIT));
- }
- }
- if (state->regs->NUMLAN_GROUPS_BIT >= 0) {
- val &= (~((state->regs->NUMLAN_GROUPS_MAX-1)<<state->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; j<state->regs->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; j<MAX_VLANS; j++) {
- reg addr = state->regs->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; i<MAX_PORTS; i++) {
- if (REG_SUPP(state->regs->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; i<MAX_VLANS; i++) {
- if (state->vlans[i].ports != 0) {
- state->num_vlans = i+1; // Hack -- we need to store the "set" vlans somewhere...
- }
- }
-
- for (i=0; i<state->regs->NUM_PORTS; i++) {
- unsigned int portmask = (1<<i);
- if (!state->vlan_enabled) {
- // Share with everybody!
- state->ports[i].shareports = (1<<state->regs->NUM_PORTS)-1;
- continue;
- }
- state->ports[i].shareports = portmask;
- for (j=0; j<MAX_VLANS; j++) {
- if (state->vlans[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<<state->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<<val->value.ports[i].id);
- state->vlans[val->port_vlan].ports |= bitmask;
- if (val->value.ports[i].flags & (1<<SWITCH_PORT_FLAG_TAGGED)) {
- state->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<<state->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<<val->port_vlan)) {
- if (state->remove_tag & (1<<val->port_vlan))
- val->value.i = 3; // shouldn't ever happen.
- else
- val->value.i = 1;
- } else {
- if (state->remove_tag & (1<<val->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<<val->port_vlan);
- state->remove_tag &= ~(1<<val->port_vlan);
-
- if (val->value.i == 0)
- state->remove_tag |= (1<<val->port_vlan);
- if (val->value.i == 1)
- state->add_tag |= (1<<val->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 <patrick.horn@gmail.com>");
-MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
-MODULE_AUTHOR("Martin Mares <mj@ucw.cz>");
-MODULE_LICENSE("GPL");
+++ /dev/null
-/*
- * Marvell 88E61xx switch driver
- *
- * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
- * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
- *
- * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/mii.h>
-#include <linux/phy.h>
-#include <linux/of.h>
-#include <linux/of_mdio.h>
-#include <linux/delay.h>
-#include <linux/switch.h>
-#include <linux/device.h>
-#include <linux/platform_device.h>
-
-#include "mvsw61xx.h"
-
-MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
-MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
-MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
-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);
+++ /dev/null
-/*
- * Marvell 88E61xx switch driver
- *
- * Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
- * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
- *
- * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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
+++ /dev/null
-/*
- * Marvell 88E6060 switch driver
- * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/kernel.h>
-#include <linux/string.h>
-#include <linux/errno.h>
-#include <linux/unistd.h>
-#include <linux/slab.h>
-#include <linux/interrupt.h>
-#include <linux/init.h>
-#include <linux/delay.h>
-#include <linux/netdevice.h>
-#include <linux/etherdevice.h>
-#include <linux/skbuff.h>
-#include <linux/spinlock.h>
-#include <linux/mm.h>
-#include <linux/module.h>
-#include <linux/mii.h>
-#include <linux/ethtool.h>
-#include <linux/phy.h>
-#include <linux/if_vlan.h>
-
-#include <asm/io.h>
-#include <asm/irq.h>
-#include <asm/uaccess.h>
-#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);
+++ /dev/null
-/*
- * Marvell 88E6060 switch driver
- * Copyright (c) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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
+++ /dev/null
-/*
- * 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 <linux/module.h>
-#include <linux/netdevice.h>
-#include <linux/switch.h>
-#include <linux/phy.h>
-
-#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 <ithamar.adema@team-embedded.nl>");
-MODULE_LICENSE("GPL");
+++ /dev/null
-/*
- * rtl8306.c: RTL8306S switch driver
- *
- * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/if.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/if_ether.h>
-#include <linux/skbuff.h>
-#include <linux/netdevice.h>
-#include <linux/netlink.h>
-#include <net/genetlink.h>
-#include <linux/switch.h>
-#include <linux/delay.h>
-#include <linux/phy.h>
-#include <linux/version.h>
-
-//#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");
-
+++ /dev/null
-/*
- * Realtek RTL8366 SMI interface driver
- *
- * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/device.h>
-#include <linux/delay.h>
-#include <linux/gpio.h>
-#include <linux/spinlock.h>
-#include <linux/skbuff.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/of_gpio.h>
-#include <linux/rtl8366.h>
-#include <linux/version.h>
-#include <linux/of_mdio.h>
-
-#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
-#include <linux/debugfs.h>
-#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 <juhosg@openwrt.org>");
-MODULE_LICENSE("GPL v2");
+++ /dev/null
-/*
- * Realtek RTL8366 SMI interface driver defines
- *
- * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/phy.h>
-#include <linux/switch.h>
-#include <linux/platform_device.h>
-#include <linux/reset.h>
-
-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 */
+++ /dev/null
-/*
- * Platform driver for the Realtek RTL8366RB ethernet switch
- *
- * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
- * Copyright (C) 2010 Roman Yeryomin <roman@advem.lv>
- * Copyright (C) 2011 Colin Leitner <colin.leitner@googlemail.com>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/device.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/delay.h>
-#include <linux/skbuff.h>
-#include <linux/rtl8366.h>
-
-#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 <juhosg@openwrt.org>");
-MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
-MODULE_AUTHOR("Roman Yeryomin <roman@advem.lv>");
-MODULE_AUTHOR("Colin Leitner <colin.leitner@googlemail.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:" RTL8366RB_DRIVER_NAME);
+++ /dev/null
-/*
- * Platform driver for the Realtek RTL8366S ethernet switch
- *
- * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (C) 2010 Antti Seppälä <a.seppala@gmail.com>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/device.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/delay.h>
-#include <linux/skbuff.h>
-#include <linux/rtl8366.h>
-
-#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 <juhosg@openwrt.org>");
-MODULE_AUTHOR("Antti Seppälä <a.seppala@gmail.com>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:" RTL8366S_DRIVER_NAME);
+++ /dev/null
-/*
- * Platform driver for the Realtek RTL8367R/M ethernet switches
- *
- * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/device.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/delay.h>
-#include <linux/skbuff.h>
-#include <linux/rtl8367.h>
-
-#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 <juhosg@openwrt.org>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:" RTL8367_DRIVER_NAME);
+++ /dev/null
-/*
- * Platform driver for the Realtek RTL8367R-VB ethernet switches
- *
- * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/device.h>
-#include <linux/of.h>
-#include <linux/of_platform.h>
-#include <linux/delay.h>
-#include <linux/skbuff.h>
-#include <linux/rtl8367.h>
-
-#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 <juhosg@openwrt.org>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:" RTL8367B_DRIVER_NAME);
-
+++ /dev/null
-/*
- * swconfig.c: Switch configuration API
- *
- * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/types.h>
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/list.h>
-#include <linux/if.h>
-#include <linux/if_ether.h>
-#include <linux/capability.h>
-#include <linux/skbuff.h>
-#include <linux/switch.h>
-#include <linux/of.h>
-#include <linux/version.h>
-#include <uapi/linux/mii.h>
-
-#define SWCONFIG_DEVNAME "switch%d"
-
-#include "swconfig_leds.c"
-
-MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
-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);
+++ /dev/null
-/*
- * swconfig_led.c: LED trigger support for the switch configuration API
- *
- * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 <linux/leds.h>
-#include <linux/ctype.h>
-#include <linux/device.h>
-#include <linux/workqueue.h>
-
-#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 */
+++ /dev/null
-/*
- * AR8216 switch driver platform data
- *
- * Copyright (C) 2012 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 */
-
+++ /dev/null
-/*
- * Copyright (c) 2008 Atheros Communications Inc.
- * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
- * Copyright (c) 2010 Daniel Golle <daniel.golle@gmail.com>
- *
- * 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 */
+++ /dev/null
-/*
- * Copyright (c) 2008 Atheros Communications Inc.
- * Copyright (c) 2009 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (c) 2009 Imre Kaloz <kaloz@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * Compex's MyLoader specific definitions
- *
- * Copyright (C) 2006-2008 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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_*/
+++ /dev/null
-/*
- * ADM6996 GPIO platform data
- *
- * Copyright (C) 2013 Hauke Mehrtens <hauke@hauke-m.de>
- *
- * 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 <linux/kernel.h>
-
-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
+++ /dev/null
-/*
- * B53 platform data
- *
- * Copyright (C) 2013 Jonas Gorski <jogo@openwrt.org>
- *
- * 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 <linux/kernel.h>
-
-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
+++ /dev/null
-/*
- * Mikrotik's RouterBOOT definitions
- *
- * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * Platform data definition for the rt2x00 driver
- *
- * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * Platform data definition for the Realtek RTL8366RB/S ethernet switch driver
- *
- * Copyright (C) 2009-2010 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * Platform data definition for the Realtek RTL8367 ethernet switch driver
- *
- * Copyright (C) 2011 Gabor Juhos <juhosg@openwrt.org>
- *
- * 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 */
+++ /dev/null
-/*
- * switch.h: Switch configuration API
- *
- * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <net/genetlink.h>
-#include <uapi/linux/switch.h>
-
-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 */
+++ /dev/null
-/*
- * switch.h: Switch configuration API
- *
- * Copyright (C) 2008 Felix Fietkau <nbd@nbd.name>
- *
- * 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 <linux/types.h>
-#include <linux/netdevice.h>
-#include <linux/netlink.h>
-#include <linux/genetlink.h>
-#ifndef __KERNEL__
-#include <netlink/netlink.h>
-#include <netlink/genl/genl.h>
-#include <netlink/genl/ctrl.h>
-#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 */