OpenWrt

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 3  →  ?path2? @ 4
/branches/gl-inet/target/linux/generic/files/Documentation/networking/adm6996.txt
@@ -0,0 +1,110 @@
-------
 
ADM6996FC / ADM6996M switch chip driver
 
 
1. General information
 
This driver supports the FC and M models only. The ADM6996F and L are
completely different chips.
Support for the FC model is extremely limited at the moment. There is no VLAN
support as of yet. The driver will not offer an swconfig interface for the FC
chip.
1.1 VLAN IDs
 
It is possible to define 16 different VLANs. Every VLAN has an identifier, its
VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the
swconfig based configuration is very straightforward. To define two VLANs with
IDs 4 and 5, you can invoke, for example:
# swconfig dev ethX vlan 4 set ports '0 1t 2 5t'
# swconfig dev ethX vlan 5 set ports '0t 1t 5t'
The swconfig framework will automatically invoke 'port Y set pvid Z' for every
port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In
this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port
is the VLAN ID associated with untagged packets coming in on that port.
But if you wish to use VLAN IDs outside the range 0-15, this automatic
behaviour of the swconfig framework becomes a problem. The 16 VLANs that
swconfig can configure on the ADM6996 also have a "vid" setting. By default,
this is the same as the number of the VLAN entry, to make the simple behaviour
above possible. To still support a VLAN with a VLAN ID higher than 15
(presumably because you are in a network where such VLAN IDs are already in
use), you can change the "vid" setting of the VLAN to anything in the range
0-1023. But suppose you did the following:
# swconfig dev ethX vlan 0 set vid 998
# swconfig dev ethX vlan 0 set ports '0 2 5t'
Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set pvid
0'. But the "pvid" should be set to 998, so you are responsible for manually
fixing this!
 
1.2 VLAN filtering
 
The switch is configured to apply source port filtering. This means that
packets are only accepted when the port the packets came in on is a member of
the VLAN the packet should go to.
 
Only membership of a VLAN is tested, it does not matter whether it is a tagged
or untagged membership.
 
For untagged packets, the destination VLAN is the Primary VLAN ID of the
incoming port. So if the PVID of a port is 0, but that port is not a member of
the VLAN with ID 0, this means that untagged packets on that port are dropped.
This can be used as a roundabout way of dropping untagged packets from a port,
a mode often referred to as "Admit only tagged packets".
 
1.3 Reset
 
The two supported chip models do not have a sofware-initiated reset. When the
driver is initialised, as well as when the 'reset' swconfig option is invoked,
the driver will set those registers it knows about and supports to the correct
default value. But there are a lot of registers in the chip that the driver
does not support. If something changed those registers, invoking 'reset' or
performing a warm reboot might still leave the chip in a "broken" state. Only
a hardware reset will bring it back in the default state.
 
2. Technical details on PHYs and the ADM6996
 
From the viewpoint of the Linux kernel, it is common that an Ethernet adapter
can be seen as a separate MAC entity and a separate PHY entity. The PHY entity
can be queried and set through registers accessible via an MDIO bus. A PHY
normally has a single address on that bus, in the range 0 through 31.
 
The ADM6996 has special-purpose registers in the range of PHYs 0 through 10.
Even though all these registers control a single ADM6996 chip, the Linux
kernel treats this as 11 separate PHYs. The driver will bind to these
addresses to prevent a different PHY driver from binding and corrupting these
registers.
 
What Linux sees as the PHY on address 0 is meant for the Ethernet MAC
connected to the CPU port of the ADM6996 switch chip (port 5). This is the
Ethernet MAC you will use to send and receive data through the switch.
 
The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of
the switch chip. These can be accessed with the Generic PHY driver, as the
registers have the common layout.
 
If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC
needs to bind to PHY address 20 for the port to work correctly.
 
The ADM6996 switch driver will reset the ports 0 through 3 on startup and when
'reset' is invoked. This could clash with a different PHY driver if the kernel
binds a PHY driver to address 16 through 19.
 
If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the ADM6996
driver will simply always report a connected 100 Mbit/s full-duplex link for
that PHY, and provide no other functionality. This is most likely not what you
want. So if you see a message in your log
 
ethX: PHY overlaps ADM6996, providing fixed PHY yy.
 
This is most likely an indication that ethX will not work properly, and your
kernel needs to be configured to attach a different PHY to that Ethernet MAC.
 
Controlling the mapping between MACs and PHYs is usually done in platform- or
board-specific fixup code. The ADM6996 driver has no influence over this.
/branches/gl-inet/target/linux/generic/files/arch/mips/fw/myloader/Makefile
@@ -0,0 +1,5 @@
#
# Makefile for the Compex's MyLoader support on MIPS architecture
#
 
lib-y += myloader.o
/branches/gl-inet/target/linux/generic/files/arch/mips/fw/myloader/myloader.c
@@ -0,0 +1,63 @@
/*
* 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;
}
/branches/gl-inet/target/linux/generic/files/drivers/leds/ledtrig-netdev.c
@@ -0,0 +1,444 @@
/*
* LED Kernel Netdev Trigger
*
* Toggles the LED to reflect the link and traffic state of a named net device
*
* Copyright 2007 Oliver Jowett <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");
/branches/gl-inet/target/linux/generic/files/drivers/misc/owl-loader.c
@@ -0,0 +1,246 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/Kconfig
@@ -0,0 +1,76 @@
config MTD_SPLIT
def_bool n
help
Generic MTD split support.
 
config MTD_SPLIT_SUPPORT
def_bool MTD = y
 
comment "Rootfs partition parsers"
 
config MTD_SPLIT_SQUASHFS_ROOT
bool "Squashfs based root partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
default n
help
This provides a parsing function which allows to detect the
offset and size of the unused portion of a rootfs partition
containing a squashfs.
 
comment "Firmware partition parsers"
 
config MTD_SPLIT_SEAMA_FW
bool "Seama firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_WRGG_FW
bool "WRGG firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_UIMAGE_FW
bool "uImage based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_FIT_FW
bool "FIT based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_LZMA_FW
bool "LZMA compressed kernel based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_TPLINK_FW
bool "TP-Link firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_TRX_FW
bool "TRX image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_BRNIMAGE_FW
bool "brnImage (brnboot image) firmware parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_EVA_FW
bool "EVA image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_MINOR_FW
bool "Mikrotik NOR image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
 
config MTD_SPLIT_JIMAGE_FW
bool "JBOOT Image based firmware partition parser"
depends on MTD_SPLIT_SUPPORT
select MTD_SPLIT
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/Makefile
@@ -0,0 +1,13 @@
obj-$(CONFIG_MTD_SPLIT) += mtdsplit.o
obj-$(CONFIG_MTD_SPLIT_SEAMA_FW) += mtdsplit_seama.o
obj-$(CONFIG_MTD_SPLIT_SQUASHFS_ROOT) += mtdsplit_squashfs.o
obj-$(CONFIG_MTD_SPLIT_UIMAGE_FW) += mtdsplit_uimage.o
obj-$(CONFIG_MTD_SPLIT_FIT_FW) += mtdsplit_fit.o
obj-$(CONFIG_MTD_SPLIT_LZMA_FW) += mtdsplit_lzma.o
obj-$(CONFIG_MTD_SPLIT_TPLINK_FW) += mtdsplit_tplink.o
obj-$(CONFIG_MTD_SPLIT_TRX_FW) += mtdsplit_trx.o
obj-$(CONFIG_MTD_SPLIT_BRNIMAGE_FW) += mtdsplit_brnimage.o
obj-$(CONFIG_MTD_SPLIT_EVA_FW) += mtdsplit_eva.o
obj-$(CONFIG_MTD_SPLIT_WRGG_FW) += mtdsplit_wrgg.o
obj-$(CONFIG_MTD_SPLIT_MINOR_FW) += mtdsplit_minor.o
obj-$(CONFIG_MTD_SPLIT_JIMAGE_FW) += mtdsplit_jimage.o
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.c
@@ -0,0 +1,130 @@
/*
* 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);
 
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit.h
@@ -0,0 +1,67 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_brnimage.c
@@ -0,0 +1,104 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_eva.c
@@ -0,0 +1,96 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_fit.c
@@ -0,0 +1,141 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_jimage.c
@@ -0,0 +1,277 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_lzma.c
@@ -0,0 +1,96 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_minor.c
@@ -0,0 +1,117 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_seama.c
@@ -0,0 +1,110 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_squashfs.c
@@ -0,0 +1,72 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_tplink.c
@@ -0,0 +1,169 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_trx.c
@@ -0,0 +1,147 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_uimage.c
@@ -0,0 +1,361 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/mtdsplit/mtdsplit_wrgg.c
@@ -0,0 +1,124 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/mtd/myloader.c
@@ -0,0 +1,182 @@
/*
* Parse MyLoader-style flash partition tables and produce a Linux partition
* array to match.
*
* Copyright (C) 2007-2009 Gabor Juhos <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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/adm6996.c
@@ -0,0 +1,1241 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/adm6996.h
@@ -0,0 +1,186 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8216.c
@@ -0,0 +1,2313 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8216.h
@@ -0,0 +1,644 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8327.c
@@ -0,0 +1,1543 @@
/*
* 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);
if (chip_is_ar8337(priv)) {
/*
* Workaround: RGMII RX delay setting needs to be
* always specified for AR8337 to avoid port 5
* RX hang on high traffic / flood conditions
*/
t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
}
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;
 
if (chip_is_ar8337(priv)) {
/* Update HOL registers with values suggested by QCA switch team */
for (i = 0; i < AR8327_NUM_PORTS; i++) {
if (i == AR8216_PORT_CPU || i == 5 || i == 6) {
t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S;
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S;
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S;
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S;
t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF_S;
t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF_S;
t |= 0x1e << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S;
} else {
t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S;
t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S;
t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S;
t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S;
t |= 0x19 << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S;
}
ar8xxx_write(priv, AR8327_REG_PORT_HOL_CTRL0(i), t);
 
t = 0x6 << AR8327_PORT_HOL_CTRL1_ING_BUF_S;
t |= AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN;
t |= AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN;
t |= AR8327_PORT_HOL_CTRL1_WRED_EN;
ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(i),
AR8327_PORT_HOL_CTRL1_ING_BUF |
AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
AR8327_PORT_HOL_CTRL1_WRED_EN,
t);
}
}
}
 
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,
.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
};
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ar8327.h
@@ -0,0 +1,346 @@
/*
* 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_CTRL0(_i) (0x970 + (_i) * 0x8)
#define AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF BITS(0, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S 0
#define AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF BITS(4, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S 4
#define AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF BITS(8, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S 8
#define AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF BITS(12, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S 12
#define AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF BITS(16, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF_S 16
#define AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF BITS(20, 4)
#define AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF_S 20
#define AR8327_PORT_HOL_CTRL0_EG_PORT_BUF BITS(24, 6)
#define AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S 24
 
#define AR8327_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
#define AR8327_PORT_HOL_CTRL1_ING_BUF BITS(0, 4)
#define AR8327_PORT_HOL_CTRL1_ING_BUF_S 0
#define AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN BIT(6)
#define AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN BIT(7)
#define AR8327_PORT_HOL_CTRL1_WRED_EN BIT(8)
#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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/Kconfig
@@ -0,0 +1,37 @@
menuconfig SWCONFIG_B53
tristate "Broadcom bcm53xx managed switch support"
depends on SWCONFIG
help
This driver adds support for Broadcom managed switch chips. It supports
BCM5325E, BCM5365, BCM539x, BCM53115 and BCM53125 as well as BCM63XX
integrated switches.
 
config SWCONFIG_B53_SPI_DRIVER
tristate "B53 SPI connected switch driver"
depends on SWCONFIG_B53 && SPI
help
Select to enable support for registering switches configured through SPI.
 
config SWCONFIG_B53_PHY_DRIVER
tristate "B53 MDIO connected switch driver"
depends on SWCONFIG_B53
select SWCONFIG_B53_PHY_FIXUP
help
Select to enable support for registering switches configured through MDIO.
 
config SWCONFIG_B53_MMAP_DRIVER
tristate "B53 MMAP connected switch driver"
depends on SWCONFIG_B53
help
Select to enable support for memory-mapped switches like the BCM63XX
integrated switches.
 
config SWCONFIG_B53_SRAB_DRIVER
tristate "B53 SRAB connected switch driver"
depends on SWCONFIG_B53
help
Select to enable support for memory-mapped Switch Register Access
Bridge Registers (SRAB) like it is found on the BCM53010
 
config SWCONFIG_B53_PHY_FIXUP
bool
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/Makefile
@@ -0,0 +1,10 @@
obj-$(CONFIG_SWCONFIG_B53) += b53_common.o
 
obj-$(CONFIG_SWCONFIG_B53_PHY_FIXUP) += b53_phy_fixup.o
 
obj-$(CONFIG_SWCONFIG_B53_MMAP_DRIVER) += b53_mmap.o
obj-$(CONFIG_SWCONFIG_B53_SRAB_DRIVER) += b53_srab.o
obj-$(CONFIG_SWCONFIG_B53_PHY_DRIVER) += b53_mdio.o
obj-$(CONFIG_SWCONFIG_B53_SPI_DRIVER) += b53_spi.o
 
ccflags-y += -Werror
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_common.c
@@ -0,0 +1,1722 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_mdio.c
@@ -0,0 +1,436 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_mmap.c
@@ -0,0 +1,241 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_phy_fixup.c
@@ -0,0 +1,55 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_priv.h
@@ -0,0 +1,341 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_regs.h
@@ -0,0 +1,348 @@
/*
* B53 register definitions
*
* Copyright (C) 2004 Broadcom Corporation
* Copyright (C) 2011-2013 Jonas Gorski <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 */
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_spi.c
@@ -0,0 +1,344 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/b53/b53_srab.c
@@ -0,0 +1,378 @@
/*
* B53 register access through Switch Register Access Bridge Registers
*
* Copyright (C) 2013 Hauke Mehrtens <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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/ip17xx.c
@@ -0,0 +1,1377 @@
/*
* 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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/mvsw61xx.c
@@ -0,0 +1,947 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/mvsw61xx.h
@@ -0,0 +1,292 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/mvswitch.c
@@ -0,0 +1,444 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/mvswitch.h
@@ -0,0 +1,145 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/psb6970.c
@@ -0,0 +1,441 @@
/*
* Lantiq PSB6970 (Tantos) Switch driver
*
* Copyright (c) 2009,2010 Team Embedded.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the
* Free Software Foundation.
*
* The switch programming done in this driver follows the
* "Ethernet Traffic Separation using VLAN" Application Note as
* published by Lantiq.
*/
 
#include <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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8306.c
@@ -0,0 +1,1066 @@
/*
* 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");
 
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366_smi.c
@@ -0,0 +1,1494 @@
/*
* 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;
}
 
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;
}
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)
{
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(true);
msleep(RTL8366_SMI_HW_STOP_DELAY);
smi->hw_reset(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;
 
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(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(true);
 
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, &reg))
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;
 
dev_info(smi->parent, "using GPIO pins %u (SDA) and %u (SCK)\n",
smi->gpio_sda, smi->gpio_sck);
 
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
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);
 
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;
 
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");
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366_smi.h
@@ -0,0 +1,155 @@
/*
* 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>
 
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)(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];
#ifdef CONFIG_RTL8366_SMI_DEBUG_FS
struct dentry *debugfs_root;
u16 dbg_reg;
u8 dbg_vlan_4k_page;
#endif
};
 
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 */
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366rb.c
@@ -0,0 +1,1532 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8366s.c
@@ -0,0 +1,1320 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8367.c
@@ -0,0 +1,1846 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/rtl8367b.c
@@ -0,0 +1,1613 @@
/*
* 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" },
{ .compatible = "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);
 
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/swconfig.c
@@ -0,0 +1,1256 @@
/*
* 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);
/branches/gl-inet/target/linux/generic/files/drivers/net/phy/swconfig_leds.c
@@ -0,0 +1,556 @@
/*
* swconfig_led.c: LED trigger support for the switch configuration API
*
* Copyright (C) 2011 Gabor Juhos <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 */
/branches/gl-inet/target/linux/generic/files/include/linux/ar8216_platform.h
@@ -0,0 +1,133 @@
/*
* 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 */
 
/branches/gl-inet/target/linux/generic/files/include/linux/ath5k_platform.h
@@ -0,0 +1,30 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/linux/ath9k_platform.h
@@ -0,0 +1,60 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/linux/myloader.h
@@ -0,0 +1,121 @@
/*
* 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_*/
/branches/gl-inet/target/linux/generic/files/include/linux/platform_data/adm6996-gpio.h
@@ -0,0 +1,29 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/include/linux/platform_data/b53.h
@@ -0,0 +1,36 @@
/*
* 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
/branches/gl-inet/target/linux/generic/files/include/linux/routerboot.h
@@ -0,0 +1,106 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/linux/rt2x00_platform.h
@@ -0,0 +1,23 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/linux/rtl8366.h
@@ -0,0 +1,40 @@
/*
* 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"
 
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)(bool active);
 
unsigned num_initvals;
struct rtl8366_initval *initvals;
};
 
enum rtl8366_type rtl8366_smi_detect(struct rtl8366_platform_data *pdata);
 
#endif /* _RTL8366_H */
/branches/gl-inet/target/linux/generic/files/include/linux/rtl8367.h
@@ -0,0 +1,60 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/linux/switch.h
@@ -0,0 +1,179 @@
/*
* 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 */
/branches/gl-inet/target/linux/generic/files/include/uapi/linux/switch.h
@@ -0,0 +1,119 @@
/*
* 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 */