mirror of https://github.com/hak5/openwrt-owl.git
change the way ./files* and the generic kernel files are applied. ./files now applies to *ALL* kernel versions, and is copied along with ./files-* - this gets rid of quite a bit of redundancy in the extra kernel drivers.
SVN-Revision: 13010owl
parent
487c622ac0
commit
1e570a9288
|
@ -31,7 +31,7 @@ else
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PATCH_DIR ?= ./patches$(shell [ -d "./patches-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
PATCH_DIR ?= ./patches$(shell [ -d "./patches-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
||||||
FILES_DIR ?= ./files$(shell [ -d "./files-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
FILES_DIR ?= $(foreach dir,$(wildcard ./files ./files-$(KERNEL_PATCHVER)),"$(dir)")
|
||||||
KERNEL_BUILD_DIR ?= $(BUILD_DIR_BASE)/linux-$(BOARD)$(if $(SUBTARGET),_$(SUBTARGET))$(if $(BUILD_SUFFIX),_$(BUILD_SUFFIX))
|
KERNEL_BUILD_DIR ?= $(BUILD_DIR_BASE)/linux-$(BOARD)$(if $(SUBTARGET),_$(SUBTARGET))$(if $(BUILD_SUFFIX),_$(BUILD_SUFFIX))
|
||||||
LINUX_DIR ?= $(KERNEL_BUILD_DIR)/linux-$(LINUX_VERSION)
|
LINUX_DIR ?= $(KERNEL_BUILD_DIR)/linux-$(LINUX_VERSION)
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,8 @@ endef
|
||||||
|
|
||||||
define Kernel/Patch/Default
|
define Kernel/Patch/Default
|
||||||
rm -rf $(PKG_BUILD_DIR)/patches; mkdir -p $(PKG_BUILD_DIR)/patches
|
rm -rf $(PKG_BUILD_DIR)/patches; mkdir -p $(PKG_BUILD_DIR)/patches
|
||||||
if [ -d $(GENERIC_FILES_DIR) ]; then $(CP) $(GENERIC_FILES_DIR)/* $(LINUX_DIR)/; fi
|
$(CP) $(foreach fdir,$(GENERIC_FILES_DIR) $(FILES_DIR),$(fdir)/.) $(LINUX_DIR)/
|
||||||
if [ -d $(FILES_DIR) ]; then \
|
find $(LINUX_DIR)/ -name \*.rej -or -name \*.orig | $(XARGS) rm -f
|
||||||
$(CP) $(FILES_DIR)/* $(LINUX_DIR)/; \
|
|
||||||
find $(LINUX_DIR)/ -name \*.rej | xargs rm -f; \
|
|
||||||
fi
|
|
||||||
$(call PatchDir,$(GENERIC_PATCH_DIR),generic/)
|
$(call PatchDir,$(GENERIC_PATCH_DIR),generic/)
|
||||||
$(call PatchDir,$(PATCH_DIR),platform/)
|
$(call PatchDir,$(PATCH_DIR),platform/)
|
||||||
endef
|
endef
|
||||||
|
|
|
@ -108,7 +108,7 @@ include $(INCLUDE_DIR)/kernel-version.mk
|
||||||
|
|
||||||
GENERIC_PLATFORM_DIR := $(TOPDIR)/target/linux/generic-$(KERNEL)
|
GENERIC_PLATFORM_DIR := $(TOPDIR)/target/linux/generic-$(KERNEL)
|
||||||
GENERIC_PATCH_DIR := $(GENERIC_PLATFORM_DIR)/patches$(shell [ -d "$(GENERIC_PLATFORM_DIR)/patches-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
GENERIC_PATCH_DIR := $(GENERIC_PLATFORM_DIR)/patches$(shell [ -d "$(GENERIC_PLATFORM_DIR)/patches-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
||||||
GENERIC_FILES_DIR := $(GENERIC_PLATFORM_DIR)/files$(shell [ -d "$(GENERIC_PLATFORM_DIR)/files-$(KERNEL_PATCHVER)" ] && printf -- "-$(KERNEL_PATCHVER)" || true )
|
GENERIC_FILES_DIR := $(foreach dir,$(wildcard $(GENERIC_PLATFORM_DIR)/files $(GENERIC_PLATFORM_DIR)/files-$(KERNEL_PATCHVER)),"$(dir)")
|
||||||
|
|
||||||
GENERIC_LINUX_CONFIG?=$(firstword $(wildcard $(GENERIC_PLATFORM_DIR)/config-$(KERNEL_PATCHVER) $(GENERIC_PLATFORM_DIR)/config-default))
|
GENERIC_LINUX_CONFIG?=$(firstword $(wildcard $(GENERIC_PLATFORM_DIR)/config-$(KERNEL_PATCHVER) $(GENERIC_PLATFORM_DIR)/config-default))
|
||||||
LINUX_CONFIG?=$(firstword $(wildcard $(foreach subdir,$(PLATFORM_DIR) $(PLATFORM_SUBDIR),$(subdir)/config-$(KERNEL_PATCHVER) $(subdir)/config-default)) $(PLATFORM_DIR)/config-$(KERNEL_PATCHVER))
|
LINUX_CONFIG?=$(firstword $(wildcard $(foreach subdir,$(PLATFORM_DIR) $(PLATFORM_SUBDIR),$(subdir)/config-$(KERNEL_PATCHVER) $(subdir)/config-default)) $(PLATFORM_DIR)/config-$(KERNEL_PATCHVER))
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
/*
|
|
||||||
* ADM6996 switch driver
|
|
||||||
*
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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 <asm/io.h>
|
|
||||||
#include <asm/irq.h>
|
|
||||||
#include <asm/uaccess.h>
|
|
||||||
#include "adm6996.h"
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("Infineon ADM6996 Switch");
|
|
||||||
MODULE_AUTHOR("Felix Fietkau");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
|
|
||||||
struct adm6996_priv {
|
|
||||||
/* use abstraction for regops, we want to add gpio support in the future */
|
|
||||||
u16 (*read)(struct phy_device *phydev, enum admreg reg);
|
|
||||||
void (*write)(struct phy_device *phydev, enum admreg reg, u16 val);
|
|
||||||
};
|
|
||||||
|
|
||||||
#define to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
|
|
||||||
|
|
||||||
|
|
||||||
static inline u16
|
|
||||||
r16(struct phy_device *pdev, enum admreg reg)
|
|
||||||
{
|
|
||||||
return to_adm(pdev)->read(pdev, reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
w16(struct phy_device *pdev, enum admreg reg, u16 val)
|
|
||||||
{
|
|
||||||
to_adm(pdev)->write(pdev, reg, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static u16
|
|
||||||
adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg)
|
|
||||||
{
|
|
||||||
return phydev->bus->read(phydev->bus, PHYADDR(reg));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val)
|
|
||||||
{
|
|
||||||
phydev->bus->write(phydev->bus, PHYADDR(reg), val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int adm6996_config_init(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
printk("%s: ADM6996 PHY driver attached.\n", pdev->attached_dev->name);
|
|
||||||
pdev->supported = ADVERTISED_100baseT_Full;
|
|
||||||
pdev->advertising = ADVERTISED_100baseT_Full;
|
|
||||||
|
|
||||||
/* initialize port and vlan settings */
|
|
||||||
for (i = 0; i < ADM_PHY_PORTS; i++) {
|
|
||||||
w16(pdev, adm_portcfg[i], ADM_PORTCFG_INIT |
|
|
||||||
ADM_PORTCFG_PVID((i == ADM_WAN_PORT) ? 1 : 0));
|
|
||||||
}
|
|
||||||
w16(pdev, adm_portcfg[5], ADM_PORTCFG_CPU);
|
|
||||||
|
|
||||||
/* reset all ports */
|
|
||||||
for (i = 0; i < ADM_PHY_PORTS; i++) {
|
|
||||||
w16(pdev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_read_status(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
phydev->speed = SPEED_100;
|
|
||||||
phydev->duplex = DUPLEX_FULL;
|
|
||||||
phydev->state = PHY_UP;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_config_aneg(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_probe(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
struct adm6996_priv *priv;
|
|
||||||
|
|
||||||
priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL);
|
|
||||||
if (priv == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
priv->read = adm6996_read_mii_reg;
|
|
||||||
priv->write = adm6996_write_mii_reg;
|
|
||||||
pdev->priv = priv;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void adm6996_remove(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
kfree(pdev->priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool adm6996_detect(struct mii_bus *bus, int addr)
|
|
||||||
{
|
|
||||||
u16 reg;
|
|
||||||
|
|
||||||
/* we only attach to phy id 0 */
|
|
||||||
if (addr != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* look for the switch on the bus */
|
|
||||||
reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK;
|
|
||||||
if (reg != ADM_SIG0_VAL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK;
|
|
||||||
if (reg != ADM_SIG1_VAL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct phy_driver adm6996_driver = {
|
|
||||||
.name = "Infineon ADM6996",
|
|
||||||
.features = PHY_BASIC_FEATURES,
|
|
||||||
.detect = adm6996_detect,
|
|
||||||
.probe = adm6996_probe,
|
|
||||||
.remove = adm6996_remove,
|
|
||||||
.config_init = &adm6996_config_init,
|
|
||||||
.config_aneg = &adm6996_config_aneg,
|
|
||||||
.read_status = &adm6996_read_status,
|
|
||||||
.driver = { .owner = THIS_MODULE,},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init adm6996_init(void)
|
|
||||||
{
|
|
||||||
return phy_driver_register(&adm6996_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit adm6996_exit(void)
|
|
||||||
{
|
|
||||||
phy_driver_unregister(&adm6996_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(adm6996_init);
|
|
||||||
module_exit(adm6996_exit);
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* ADM6996 switch driver
|
|
||||||
*
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
#define ADM_PHY_PORTS 5
|
|
||||||
#define ADM_CPU_PORT 5
|
|
||||||
#define ADM_WAN_PORT 0 /* FIXME: dynamic ? */
|
|
||||||
|
|
||||||
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_EEPROM_EXT_BASE = 0x40,
|
|
||||||
ADM_COUNTER_BASE = 0xa0,
|
|
||||||
ADM_SIG0 = ADM_COUNTER_BASE + 0,
|
|
||||||
ADM_SIG1 = ADM_COUNTER_BASE + 1,
|
|
||||||
ADM_PHY_BASE = 0x200,
|
|
||||||
#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Chip identification patterns */
|
|
||||||
#define ADM_SIG0_MASK 0xfff0
|
|
||||||
#define ADM_SIG0_VAL 0x1020
|
|
||||||
#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)
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
|
@ -1,474 +0,0 @@
|
||||||
/*
|
|
||||||
* Marvell 88E6060 switch driver
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
|
|
||||||
struct mvswitch_priv {
|
|
||||||
/* the driver's tx function */
|
|
||||||
int (*hardstart)(struct sk_buff *skb, struct net_device *dev);
|
|
||||||
struct vlan_group *grp;
|
|
||||||
u8 vlans[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv)
|
|
||||||
|
|
||||||
static inline u16
|
|
||||||
r16(struct phy_device *phydev, int addr, int reg)
|
|
||||||
{
|
|
||||||
return phydev->bus->read(phydev->bus, addr, reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
w16(struct phy_device *phydev, int addr, int reg, u16 val)
|
|
||||||
{
|
|
||||||
phydev->bus->write(phydev->bus, addr, reg, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev)
|
|
||||||
{
|
|
||||||
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 priv->hardstart(skb, dev);
|
|
||||||
|
|
||||||
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 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_mangle_rx(struct sk_buff *skb, int napi)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv;
|
|
||||||
struct net_device *dev;
|
|
||||||
int vlan = -1;
|
|
||||||
unsigned char *buf;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
dev = skb->dev;
|
|
||||||
if (!dev)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
priv = dev->phy_ptr;
|
|
||||||
if (!priv)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (!priv->grp)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
#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)
|
|
||||||
goto error;
|
|
||||||
#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)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
|
||||||
|
|
||||||
if (napi)
|
|
||||||
return vlan_hwaccel_receive_skb(skb, priv->grp, vlan);
|
|
||||||
else
|
|
||||||
return vlan_hwaccel_rx(skb, priv->grp, vlan);
|
|
||||||
|
|
||||||
error:
|
|
||||||
/* no vlan? eat the packet! */
|
|
||||||
dev_kfree_skb_any(skb);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_netif_rx(struct sk_buff *skb)
|
|
||||||
{
|
|
||||||
return mvswitch_mangle_rx(skb, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_netif_receive_skb(struct sk_buff *skb)
|
|
||||||
{
|
|
||||||
return mvswitch_mangle_rx(skb, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
mvswitch_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv = dev->phy_ptr;
|
|
||||||
priv->grp = grp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
dev->irq = PHY_POLL;
|
|
||||||
|
|
||||||
/* initialize default vlans */
|
|
||||||
for (i = 0; i < MV_PORTS; i++)
|
|
||||||
priv->vlans[(i == MV_WANPORT ? 1 : 0)] |= (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
|
|
||||||
);
|
|
||||||
|
|
||||||
/* hook into the tx function */
|
|
||||||
priv->hardstart = dev->hard_start_xmit;
|
|
||||||
pdev->netif_receive_skb = mvswitch_netif_receive_skb;
|
|
||||||
pdev->netif_rx = mvswitch_netif_rx;
|
|
||||||
dev->hard_start_xmit = mvswitch_mangle_tx;
|
|
||||||
dev->vlan_rx_register = mvswitch_vlan_rx_register;
|
|
||||||
#ifdef HEADER_MODE
|
|
||||||
dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX;
|
|
||||||
#else
|
|
||||||
dev->features |= NETIF_F_HW_VLAN_RX;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_read_status(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
pdev->speed = SPEED_100;
|
|
||||||
pdev->duplex = DUPLEX_FULL;
|
|
||||||
pdev->state = PHY_UP;
|
|
||||||
|
|
||||||
/* 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_config_aneg(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
mvswitch_remove(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv = to_mvsw(pdev);
|
|
||||||
struct net_device *dev = pdev->attached_dev;
|
|
||||||
|
|
||||||
/* restore old xmit handler */
|
|
||||||
if (priv->hardstart && dev)
|
|
||||||
dev->hard_start_xmit = priv->hardstart;
|
|
||||||
dev->vlan_rx_register = NULL;
|
|
||||||
dev->vlan_rx_kill_vid = NULL;
|
|
||||||
dev->phy_ptr = NULL;
|
|
||||||
dev->features &= ~NETIF_F_HW_VLAN_RX;
|
|
||||||
kfree(priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
mvswitch_detect(struct mii_bus *bus, int addr)
|
|
||||||
{
|
|
||||||
u16 reg;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* we attach to phy id 31 to make sure that the late probe works */
|
|
||||||
if (addr != 31)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* look for the switch on the bus */
|
|
||||||
reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
|
|
||||||
if (reg != MV_IDENT_VALUE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now that we've established that the switch actually exists, let's
|
|
||||||
* get rid of the competition :)
|
|
||||||
*/
|
|
||||||
for (i = 0; i < 31; i++) {
|
|
||||||
if (!bus->phy_map[i])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
device_unregister(&bus->phy_map[i]->dev);
|
|
||||||
kfree(bus->phy_map[i]);
|
|
||||||
bus->phy_map[i] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 struct phy_driver mvswitch_driver = {
|
|
||||||
.name = "Marvell 88E6060",
|
|
||||||
.features = PHY_BASIC_FEATURES,
|
|
||||||
.detect = &mvswitch_detect,
|
|
||||||
.probe = &mvswitch_probe,
|
|
||||||
.remove = &mvswitch_remove,
|
|
||||||
.config_init = &mvswitch_config_init,
|
|
||||||
.config_aneg = &mvswitch_config_aneg,
|
|
||||||
.read_status = &mvswitch_read_status,
|
|
||||||
.driver = { .owner = THIS_MODULE,},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init
|
|
||||||
mvswitch_init(void)
|
|
||||||
{
|
|
||||||
return phy_driver_register(&mvswitch_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit
|
|
||||||
mvswitch_exit(void)
|
|
||||||
{
|
|
||||||
phy_driver_unregister(&mvswitch_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(mvswitch_init);
|
|
||||||
module_exit(mvswitch_exit);
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
* Marvell 88E6060 switch driver
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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
|
|
|
@ -1,170 +0,0 @@
|
||||||
/*
|
|
||||||
* ADM6996 switch driver
|
|
||||||
*
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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 <asm/io.h>
|
|
||||||
#include <asm/irq.h>
|
|
||||||
#include <asm/uaccess.h>
|
|
||||||
#include "adm6996.h"
|
|
||||||
|
|
||||||
MODULE_DESCRIPTION("Infineon ADM6996 Switch");
|
|
||||||
MODULE_AUTHOR("Felix Fietkau");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
|
|
||||||
struct adm6996_priv {
|
|
||||||
/* use abstraction for regops, we want to add gpio support in the future */
|
|
||||||
u16 (*read)(struct phy_device *phydev, enum admreg reg);
|
|
||||||
void (*write)(struct phy_device *phydev, enum admreg reg, u16 val);
|
|
||||||
};
|
|
||||||
|
|
||||||
#define to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
|
|
||||||
|
|
||||||
|
|
||||||
static inline u16
|
|
||||||
r16(struct phy_device *pdev, enum admreg reg)
|
|
||||||
{
|
|
||||||
return to_adm(pdev)->read(pdev, reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
w16(struct phy_device *pdev, enum admreg reg, u16 val)
|
|
||||||
{
|
|
||||||
to_adm(pdev)->write(pdev, reg, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static u16
|
|
||||||
adm6996_read_mii_reg(struct phy_device *phydev, enum admreg reg)
|
|
||||||
{
|
|
||||||
return phydev->bus->read(phydev->bus, PHYADDR(reg));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
adm6996_write_mii_reg(struct phy_device *phydev, enum admreg reg, u16 val)
|
|
||||||
{
|
|
||||||
phydev->bus->write(phydev->bus, PHYADDR(reg), val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int adm6996_config_init(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
printk("%s: ADM6996 PHY driver attached.\n", pdev->attached_dev->name);
|
|
||||||
pdev->supported = ADVERTISED_100baseT_Full;
|
|
||||||
pdev->advertising = ADVERTISED_100baseT_Full;
|
|
||||||
|
|
||||||
/* initialize port and vlan settings */
|
|
||||||
for (i = 0; i < ADM_PHY_PORTS; i++) {
|
|
||||||
w16(pdev, adm_portcfg[i], ADM_PORTCFG_INIT |
|
|
||||||
ADM_PORTCFG_PVID((i == ADM_WAN_PORT) ? 1 : 0));
|
|
||||||
}
|
|
||||||
w16(pdev, adm_portcfg[5], ADM_PORTCFG_CPU);
|
|
||||||
|
|
||||||
/* reset all ports */
|
|
||||||
for (i = 0; i < ADM_PHY_PORTS; i++) {
|
|
||||||
w16(pdev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_read_status(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
phydev->speed = SPEED_100;
|
|
||||||
phydev->duplex = DUPLEX_FULL;
|
|
||||||
phydev->state = PHY_UP;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_config_aneg(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int adm6996_probe(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
struct adm6996_priv *priv;
|
|
||||||
|
|
||||||
priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL);
|
|
||||||
if (priv == NULL)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
priv->read = adm6996_read_mii_reg;
|
|
||||||
priv->write = adm6996_write_mii_reg;
|
|
||||||
pdev->priv = priv;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void adm6996_remove(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
kfree(pdev->priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool adm6996_detect(struct mii_bus *bus, int addr)
|
|
||||||
{
|
|
||||||
u16 reg;
|
|
||||||
|
|
||||||
/* we only attach to phy id 0 */
|
|
||||||
if (addr != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* look for the switch on the bus */
|
|
||||||
reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK;
|
|
||||||
if (reg != ADM_SIG0_VAL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
reg = bus->read(bus, PHYADDR(ADM_SIG1)) & ADM_SIG1_MASK;
|
|
||||||
if (reg != ADM_SIG1_VAL)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct phy_driver adm6996_driver = {
|
|
||||||
.name = "Infineon ADM6996",
|
|
||||||
.features = PHY_BASIC_FEATURES,
|
|
||||||
.detect = adm6996_detect,
|
|
||||||
.probe = adm6996_probe,
|
|
||||||
.remove = adm6996_remove,
|
|
||||||
.config_init = &adm6996_config_init,
|
|
||||||
.config_aneg = &adm6996_config_aneg,
|
|
||||||
.read_status = &adm6996_read_status,
|
|
||||||
.driver = { .owner = THIS_MODULE,},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init adm6996_init(void)
|
|
||||||
{
|
|
||||||
return phy_driver_register(&adm6996_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit adm6996_exit(void)
|
|
||||||
{
|
|
||||||
phy_driver_unregister(&adm6996_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(adm6996_init);
|
|
||||||
module_exit(adm6996_exit);
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* ADM6996 switch driver
|
|
||||||
*
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
#define ADM_PHY_PORTS 5
|
|
||||||
#define ADM_CPU_PORT 5
|
|
||||||
#define ADM_WAN_PORT 0 /* FIXME: dynamic ? */
|
|
||||||
|
|
||||||
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_EEPROM_EXT_BASE = 0x40,
|
|
||||||
ADM_COUNTER_BASE = 0xa0,
|
|
||||||
ADM_SIG0 = ADM_COUNTER_BASE + 0,
|
|
||||||
ADM_SIG1 = ADM_COUNTER_BASE + 1,
|
|
||||||
ADM_PHY_BASE = 0x200,
|
|
||||||
#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Chip identification patterns */
|
|
||||||
#define ADM_SIG0_MASK 0xfff0
|
|
||||||
#define ADM_SIG0_VAL 0x1020
|
|
||||||
#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)
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
|
@ -1,474 +0,0 @@
|
||||||
/*
|
|
||||||
* Marvell 88E6060 switch driver
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
|
|
||||||
struct mvswitch_priv {
|
|
||||||
/* the driver's tx function */
|
|
||||||
int (*hardstart)(struct sk_buff *skb, struct net_device *dev);
|
|
||||||
struct vlan_group *grp;
|
|
||||||
u8 vlans[16];
|
|
||||||
};
|
|
||||||
|
|
||||||
#define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv)
|
|
||||||
|
|
||||||
static inline u16
|
|
||||||
r16(struct phy_device *phydev, int addr, int reg)
|
|
||||||
{
|
|
||||||
return phydev->bus->read(phydev->bus, addr, reg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
w16(struct phy_device *phydev, int addr, int reg, u16 val)
|
|
||||||
{
|
|
||||||
phydev->bus->write(phydev->bus, addr, reg, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev)
|
|
||||||
{
|
|
||||||
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 priv->hardstart(skb, dev);
|
|
||||||
|
|
||||||
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 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_mangle_rx(struct sk_buff *skb, int napi)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv;
|
|
||||||
struct net_device *dev;
|
|
||||||
int vlan = -1;
|
|
||||||
unsigned char *buf;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
dev = skb->dev;
|
|
||||||
if (!dev)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
priv = dev->phy_ptr;
|
|
||||||
if (!priv)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
if (!priv->grp)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
#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)
|
|
||||||
goto error;
|
|
||||||
#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)
|
|
||||||
goto error;
|
|
||||||
|
|
||||||
skb->protocol = eth_type_trans(skb, skb->dev);
|
|
||||||
|
|
||||||
if (napi)
|
|
||||||
return vlan_hwaccel_receive_skb(skb, priv->grp, vlan);
|
|
||||||
else
|
|
||||||
return vlan_hwaccel_rx(skb, priv->grp, vlan);
|
|
||||||
|
|
||||||
error:
|
|
||||||
/* no vlan? eat the packet! */
|
|
||||||
dev_kfree_skb_any(skb);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_netif_rx(struct sk_buff *skb)
|
|
||||||
{
|
|
||||||
return mvswitch_mangle_rx(skb, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_netif_receive_skb(struct sk_buff *skb)
|
|
||||||
{
|
|
||||||
return mvswitch_mangle_rx(skb, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void
|
|
||||||
mvswitch_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv = dev->phy_ptr;
|
|
||||||
priv->grp = grp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
dev->irq = PHY_POLL;
|
|
||||||
|
|
||||||
/* initialize default vlans */
|
|
||||||
for (i = 0; i < MV_PORTS; i++)
|
|
||||||
priv->vlans[(i == MV_WANPORT ? 1 : 0)] |= (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
|
|
||||||
);
|
|
||||||
|
|
||||||
/* hook into the tx function */
|
|
||||||
priv->hardstart = dev->hard_start_xmit;
|
|
||||||
pdev->netif_receive_skb = mvswitch_netif_receive_skb;
|
|
||||||
pdev->netif_rx = mvswitch_netif_rx;
|
|
||||||
dev->hard_start_xmit = mvswitch_mangle_tx;
|
|
||||||
dev->vlan_rx_register = mvswitch_vlan_rx_register;
|
|
||||||
#ifdef HEADER_MODE
|
|
||||||
dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX;
|
|
||||||
#else
|
|
||||||
dev->features |= NETIF_F_HW_VLAN_RX;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
mvswitch_read_status(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
pdev->speed = SPEED_100;
|
|
||||||
pdev->duplex = DUPLEX_FULL;
|
|
||||||
pdev->state = PHY_UP;
|
|
||||||
|
|
||||||
/* 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_config_aneg(struct phy_device *phydev)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
mvswitch_remove(struct phy_device *pdev)
|
|
||||||
{
|
|
||||||
struct mvswitch_priv *priv = to_mvsw(pdev);
|
|
||||||
struct net_device *dev = pdev->attached_dev;
|
|
||||||
|
|
||||||
/* restore old xmit handler */
|
|
||||||
if (priv->hardstart && dev)
|
|
||||||
dev->hard_start_xmit = priv->hardstart;
|
|
||||||
dev->vlan_rx_register = NULL;
|
|
||||||
dev->vlan_rx_kill_vid = NULL;
|
|
||||||
dev->phy_ptr = NULL;
|
|
||||||
dev->features &= ~NETIF_F_HW_VLAN_RX;
|
|
||||||
kfree(priv);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
mvswitch_detect(struct mii_bus *bus, int addr)
|
|
||||||
{
|
|
||||||
u16 reg;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
/* we attach to phy id 31 to make sure that the late probe works */
|
|
||||||
if (addr != 31)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/* look for the switch on the bus */
|
|
||||||
reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
|
|
||||||
if (reg != MV_IDENT_VALUE)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Now that we've established that the switch actually exists, let's
|
|
||||||
* get rid of the competition :)
|
|
||||||
*/
|
|
||||||
for (i = 0; i < 31; i++) {
|
|
||||||
if (!bus->phy_map[i])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
device_unregister(&bus->phy_map[i]->dev);
|
|
||||||
kfree(bus->phy_map[i]);
|
|
||||||
bus->phy_map[i] = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 struct phy_driver mvswitch_driver = {
|
|
||||||
.name = "Marvell 88E6060",
|
|
||||||
.features = PHY_BASIC_FEATURES,
|
|
||||||
.detect = &mvswitch_detect,
|
|
||||||
.probe = &mvswitch_probe,
|
|
||||||
.remove = &mvswitch_remove,
|
|
||||||
.config_init = &mvswitch_config_init,
|
|
||||||
.config_aneg = &mvswitch_config_aneg,
|
|
||||||
.read_status = &mvswitch_read_status,
|
|
||||||
.driver = { .owner = THIS_MODULE,},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int __init
|
|
||||||
mvswitch_init(void)
|
|
||||||
{
|
|
||||||
return phy_driver_register(&mvswitch_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit
|
|
||||||
mvswitch_exit(void)
|
|
||||||
{
|
|
||||||
phy_driver_unregister(&mvswitch_driver);
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(mvswitch_init);
|
|
||||||
module_exit(mvswitch_exit);
|
|
|
@ -1,145 +0,0 @@
|
||||||
/*
|
|
||||||
* Marvell 88E6060 switch driver
|
|
||||||
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
*
|
|
||||||
* 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
|
|
|
@ -1,872 +0,0 @@
|
||||||
/*
|
|
||||||
* swconfig.c: Switch configuration API
|
|
||||||
*
|
|
||||||
* Copyright (C) 2008 Felix Fietkau <nbd@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/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>
|
|
||||||
|
|
||||||
//#define DEBUG 1
|
|
||||||
#ifdef DEBUG
|
|
||||||
#define DPRINTF(format, ...) printk("%s: " format, __func__, ##__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define DPRINTF(...) do {} while(0)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
|
|
||||||
static int swdev_id = 0;
|
|
||||||
static struct list_head swdevs;
|
|
||||||
static spinlock_t swdevs_lock = SPIN_LOCK_UNLOCKED;
|
|
||||||
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, struct switch_attr *attr, struct switch_val *val)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
if (val->port_vlan >= dev->vlans)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if (!dev->get_vlan_ports)
|
|
||||||
return -EOPNOTSUPP;
|
|
||||||
|
|
||||||
ret = dev->get_vlan_ports(dev, val);
|
|
||||||
printk("SET PORTS %d\n", val->len);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
swconfig_set_vlan_ports(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (val->port_vlan >= dev->vlans)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
/* validate ports */
|
|
||||||
if (val->len > dev->ports)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
for (i = 0; i < val->len; i++) {
|
|
||||||
if (val->value.ports[i].id >= dev->ports)
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dev->set_vlan_ports)
|
|
||||||
return -EOPNOTSUPP;
|
|
||||||
|
|
||||||
printk("SET PORTS %d\n", val->len);
|
|
||||||
return dev->set_vlan_ports(dev, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
swconfig_apply_config(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val)
|
|
||||||
{
|
|
||||||
/* don't complain if not supported by the switch driver */
|
|
||||||
if (!dev->apply_config)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return dev->apply_config(dev);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
enum global_defaults {
|
|
||||||
GLOBAL_APPLY,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum vlan_defaults {
|
|
||||||
VLAN_PORTS,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum port_defaults {
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct switch_attr default_port[] = {
|
|
||||||
[PORT_LINK] = {
|
|
||||||
.type = SWITCH_TYPE_INT,
|
|
||||||
.name = "link",
|
|
||||||
.description = "Current link speed",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 void swconfig_defaults_init(struct switch_dev *dev)
|
|
||||||
{
|
|
||||||
dev->def_global = 0;
|
|
||||||
dev->def_vlan = 0;
|
|
||||||
dev->def_port = 0;
|
|
||||||
|
|
||||||
if (dev->get_vlan_ports || dev->set_vlan_ports)
|
|
||||||
set_bit(VLAN_PORTS, &dev->def_vlan);
|
|
||||||
|
|
||||||
/* always present, can be no-op */
|
|
||||||
set_bit(GLOBAL_APPLY, &dev->def_global);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static struct genl_family switch_fam = {
|
|
||||||
.id = GENL_ID_GENERATE,
|
|
||||||
.name = "switch",
|
|
||||||
.hdrsize = 0,
|
|
||||||
.version = 1,
|
|
||||||
.maxattr = SWITCH_ATTR_MAX,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 inline void
|
|
||||||
swconfig_lock(void)
|
|
||||||
{
|
|
||||||
spin_lock(&swdevs_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
swconfig_unlock(void)
|
|
||||||
{
|
|
||||||
spin_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)
|
|
||||||
spin_lock(&dev->lock);
|
|
||||||
else
|
|
||||||
DPRINTF("device %d not found\n", id);
|
|
||||||
swconfig_unlock();
|
|
||||||
done:
|
|
||||||
return dev;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
swconfig_put_dev(struct switch_dev *dev)
|
|
||||||
{
|
|
||||||
spin_unlock(&dev->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
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_pid, info->snd_seq, &switch_fam,
|
|
||||||
NLM_F_MULTI, SWITCH_CMD_NEW_ATTR);
|
|
||||||
if (IS_ERR(hdr))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, id);
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_OP_TYPE, op->type);
|
|
||||||
NLA_PUT_STRING(msg, SWITCH_ATTR_OP_NAME, op->name);
|
|
||||||
if (op->description)
|
|
||||||
NLA_PUT_STRING(msg, SWITCH_ATTR_OP_DESCRIPTION,
|
|
||||||
op->description);
|
|
||||||
|
|
||||||
return genlmsg_end(msg, hdr);
|
|
||||||
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_unicast(cb->msg, info->snd_pid);
|
|
||||||
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->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->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->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, &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, &def_list[i]);
|
|
||||||
if (err < 0)
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
swconfig_put_dev(dev);
|
|
||||||
|
|
||||||
if (!cb.msg)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return genlmsg_unicast(cb.msg, info->snd_pid);
|
|
||||||
|
|
||||||
error:
|
|
||||||
if (cb.msg)
|
|
||||||
nlmsg_free(cb.msg);
|
|
||||||
out:
|
|
||||||
swconfig_put_dev(dev);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static 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;
|
|
||||||
struct switch_attr *attr = NULL;
|
|
||||||
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->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->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]);
|
|
||||||
break;
|
|
||||||
case SWITCH_CMD_SET_PORT:
|
|
||||||
case SWITCH_CMD_GET_PORT:
|
|
||||||
alist = &dev->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]);
|
|
||||||
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)
|
|
||||||
DPRINTF("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 = &val->value.ports[val->len];
|
|
||||||
|
|
||||||
if (val->len >= max)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
if (nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, nla,
|
|
||||||
port_policy))
|
|
||||||
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_set_attr(struct sk_buff *skb, struct genl_info *info)
|
|
||||||
{
|
|
||||||
struct switch_attr *attr;
|
|
||||||
struct switch_dev *dev;
|
|
||||||
struct switch_val val;
|
|
||||||
int err = -EINVAL;
|
|
||||||
|
|
||||||
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;
|
|
||||||
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;
|
|
||||||
|
|
||||||
NLA_PUT_U32(cb->msg, SWITCH_PORT_ID, port->id);
|
|
||||||
if (port->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
|
|
||||||
NLA_PUT_FLAG(cb->msg, SWITCH_PORT_FLAG_TAGGED);
|
|
||||||
|
|
||||||
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_get_attr(struct sk_buff *skb, struct genl_info *info)
|
|
||||||
{
|
|
||||||
struct genlmsghdr *hdr = nlmsg_data(info->nlhdr);
|
|
||||||
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_dev;
|
|
||||||
|
|
||||||
if (attr->type == SWITCH_TYPE_PORTS) {
|
|
||||||
val.value.ports = dev->portbuf;
|
|
||||||
memset(dev->portbuf, 0,
|
|
||||||
sizeof(struct switch_port) * dev->ports);
|
|
||||||
}
|
|
||||||
|
|
||||||
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_pid, info->snd_seq, &switch_fam,
|
|
||||||
0, cmd);
|
|
||||||
if (IS_ERR(hdr))
|
|
||||||
goto nla_put_failure;
|
|
||||||
|
|
||||||
switch(attr->type) {
|
|
||||||
case SWITCH_TYPE_INT:
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val.value.i);
|
|
||||||
break;
|
|
||||||
case SWITCH_TYPE_STRING:
|
|
||||||
NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val.value.s);
|
|
||||||
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;
|
|
||||||
default:
|
|
||||||
DPRINTF("invalid type in attribute\n");
|
|
||||||
err = -EINVAL;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
err = genlmsg_end(msg, hdr);
|
|
||||||
if (err < 0)
|
|
||||||
goto nla_put_failure;
|
|
||||||
|
|
||||||
swconfig_put_dev(dev);
|
|
||||||
return genlmsg_unicast(msg, info->snd_pid);
|
|
||||||
|
|
||||||
nla_put_failure:
|
|
||||||
if (msg)
|
|
||||||
nlmsg_free(msg);
|
|
||||||
error_dev:
|
|
||||||
swconfig_put_dev(dev);
|
|
||||||
error:
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
void *hdr;
|
|
||||||
|
|
||||||
hdr = genlmsg_put(msg, pid, seq, &switch_fam, flags,
|
|
||||||
SWITCH_CMD_NEW_ATTR);
|
|
||||||
if (IS_ERR(hdr))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_ID, dev->id);
|
|
||||||
NLA_PUT_STRING(msg, SWITCH_ATTR_NAME, dev->name);
|
|
||||||
NLA_PUT_STRING(msg, SWITCH_ATTR_DEV_NAME, dev->devname);
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_VLANS, dev->vlans);
|
|
||||||
NLA_PUT_U32(msg, SWITCH_ATTR_PORTS, dev->ports);
|
|
||||||
|
|
||||||
return genlmsg_end(msg, hdr);
|
|
||||||
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).pid,
|
|
||||||
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,
|
|
||||||
.doit = swconfig_set_attr,
|
|
||||||
.policy = switch_policy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cmd = SWITCH_CMD_SET_VLAN,
|
|
||||||
.doit = swconfig_set_attr,
|
|
||||||
.policy = switch_policy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cmd = SWITCH_CMD_SET_PORT,
|
|
||||||
.doit = swconfig_set_attr,
|
|
||||||
.policy = switch_policy,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.cmd = SWITCH_CMD_GET_SWITCH,
|
|
||||||
.dumpit = swconfig_dump_switches,
|
|
||||||
.policy = switch_policy,
|
|
||||||
.done = swconfig_done,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
|
||||||
register_switch(struct switch_dev *dev, struct net_device *netdev)
|
|
||||||
{
|
|
||||||
INIT_LIST_HEAD(&dev->dev_list);
|
|
||||||
if (netdev) {
|
|
||||||
dev->netdev = netdev;
|
|
||||||
if (!dev->devname)
|
|
||||||
dev->devname = netdev->name;
|
|
||||||
}
|
|
||||||
BUG_ON(!dev->devname);
|
|
||||||
|
|
||||||
if (dev->ports > 0) {
|
|
||||||
dev->portbuf = kzalloc(sizeof(struct switch_port) * dev->ports,
|
|
||||||
GFP_KERNEL);
|
|
||||||
if (!dev->portbuf)
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
dev->id = ++swdev_id;
|
|
||||||
swconfig_defaults_init(dev);
|
|
||||||
spin_lock_init(&dev->lock);
|
|
||||||
swconfig_lock();
|
|
||||||
list_add(&dev->dev_list, &swdevs);
|
|
||||||
swconfig_unlock();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(register_switch);
|
|
||||||
|
|
||||||
void
|
|
||||||
unregister_switch(struct switch_dev *dev)
|
|
||||||
{
|
|
||||||
kfree(dev->portbuf);
|
|
||||||
spin_lock(&dev->lock);
|
|
||||||
swconfig_lock();
|
|
||||||
list_del(&dev->dev_list);
|
|
||||||
swconfig_unlock();
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL_GPL(unregister_switch);
|
|
||||||
|
|
||||||
|
|
||||||
static int __init
|
|
||||||
swconfig_init(void)
|
|
||||||
{
|
|
||||||
int i, err;
|
|
||||||
|
|
||||||
INIT_LIST_HEAD(&swdevs);
|
|
||||||
err = genl_register_family(&switch_fam);
|
|
||||||
if (err)
|
|
||||||
return err;
|
|
||||||
|
|
||||||
for (i = 0; i < ARRAY_SIZE(swconfig_ops); i++) {
|
|
||||||
err = genl_register_ops(&switch_fam, &swconfig_ops[i]);
|
|
||||||
if (err)
|
|
||||||
goto unregister;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
unregister:
|
|
||||||
genl_unregister_family(&switch_fam);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __exit
|
|
||||||
swconfig_exit(void)
|
|
||||||
{
|
|
||||||
genl_unregister_family(&switch_fam);
|
|
||||||
}
|
|
||||||
|
|
||||||
module_init(swconfig_init);
|
|
||||||
module_exit(swconfig_exit);
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2001 MontaVista Software Inc.
|
|
||||||
* Author: MontaVista Software, Inc.
|
|
||||||
* stevel@mvista.com or source@mvista.com
|
|
||||||
*
|
|
||||||
* 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
||||||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
||||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/pci.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
|
|
||||||
#include <asm/pci.h>
|
|
||||||
#include <asm/io.h>
|
|
||||||
|
|
||||||
#include <asm/rc32434/rc32434.h>
|
|
||||||
|
|
||||||
static int __devinitdata irq_map[2][12] = {
|
|
||||||
{ 0, 0, 2, 3, 2, 3, 0, 0, 0, 0, 0, 1 },
|
|
||||||
{ 0, 0, 1, 3, 0, 2, 1, 3, 0, 2, 1, 3 }
|
|
||||||
};
|
|
||||||
|
|
||||||
int __init pcibios_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
|
|
||||||
{
|
|
||||||
int irq = 0;
|
|
||||||
|
|
||||||
if (dev->bus->number < 2 && PCI_SLOT(dev->devfn) < 12) {
|
|
||||||
irq = irq_map[dev->bus->number][PCI_SLOT(dev->devfn)];
|
|
||||||
}
|
|
||||||
return irq + GROUP4_IRQ_BASE + 4;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,218 +0,0 @@
|
||||||
/**************************************************************************
|
|
||||||
*
|
|
||||||
* BRIEF MODULE DESCRIPTION
|
|
||||||
* pci_ops for IDT EB434 board
|
|
||||||
*
|
|
||||||
* Copyright 2004 IDT Inc. (rischelp@idt.com)
|
|
||||||
* Copyright 2006 Felix Fietkau <nbd@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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
||||||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
||||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**************************************************************************
|
|
||||||
* May 2004 rkt, neb
|
|
||||||
*
|
|
||||||
* Initial Release
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/autoconf.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/pci.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
|
|
||||||
#include <asm/cpu.h>
|
|
||||||
#include <asm/io.h>
|
|
||||||
|
|
||||||
#include <asm/rc32434/rc32434.h>
|
|
||||||
#include <asm/rc32434/pci.h>
|
|
||||||
|
|
||||||
#define PCI_ACCESS_READ 0
|
|
||||||
#define PCI_ACCESS_WRITE 1
|
|
||||||
|
|
||||||
|
|
||||||
#define PCI_CFG_SET(bus,slot,func,off) \
|
|
||||||
(rc32434_pci->pcicfga = (0x80000000 | \
|
|
||||||
((bus) << 16) | ((slot)<<11) | \
|
|
||||||
((func)<<8) | (off)))
|
|
||||||
|
|
||||||
static inline int config_access(unsigned char access_type, struct pci_bus *bus,
|
|
||||||
unsigned int devfn, unsigned char where,
|
|
||||||
u32 * data)
|
|
||||||
{
|
|
||||||
unsigned int slot = PCI_SLOT(devfn);
|
|
||||||
u8 func = PCI_FUNC(devfn);
|
|
||||||
|
|
||||||
/* Setup address */
|
|
||||||
PCI_CFG_SET(bus->number, slot, func, where);
|
|
||||||
rc32434_sync();
|
|
||||||
|
|
||||||
if (access_type == PCI_ACCESS_WRITE)
|
|
||||||
rc32434_pci->pcicfgd = *data;
|
|
||||||
else
|
|
||||||
*data = rc32434_pci->pcicfgd;
|
|
||||||
|
|
||||||
rc32434_sync();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We can't address 8 and 16 bit words directly. Instead we have to
|
|
||||||
* read/write a 32bit word and mask/modify the data we actually want.
|
|
||||||
*/
|
|
||||||
static int read_config_byte(struct pci_bus *bus, unsigned int devfn,
|
|
||||||
int where, u8 * val)
|
|
||||||
{
|
|
||||||
u32 data;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = config_access(PCI_ACCESS_READ, bus, devfn, where, &data);
|
|
||||||
*val = (data >> ((where & 3) << 3)) & 0xff;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int read_config_word(struct pci_bus *bus, unsigned int devfn,
|
|
||||||
int where, u16 * val)
|
|
||||||
{
|
|
||||||
u32 data;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = config_access(PCI_ACCESS_READ, bus, devfn, where, &data);
|
|
||||||
*val = (data >> ((where & 3) << 3)) & 0xffff;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int read_config_dword(struct pci_bus *bus, unsigned int devfn,
|
|
||||||
int where, u32 * val)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
int delay = 1;
|
|
||||||
|
|
||||||
if (bus->number == 0 && PCI_SLOT(devfn) > 21)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
retry:
|
|
||||||
ret = config_access(PCI_ACCESS_READ, bus, devfn, where, val);
|
|
||||||
|
|
||||||
/* PCI scan: check for invalid values, device may not have
|
|
||||||
* finished initializing */
|
|
||||||
|
|
||||||
if (where == PCI_VENDOR_ID) {
|
|
||||||
if (ret == 0xffffffff || ret == 0x00000000 ||
|
|
||||||
ret == 0x0000ffff || ret == 0xffff0000) {
|
|
||||||
|
|
||||||
if (delay > 4)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
delay *= 2;
|
|
||||||
msleep(delay);
|
|
||||||
goto retry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_config_byte(struct pci_bus *bus, unsigned int devfn, int where,
|
|
||||||
u8 val)
|
|
||||||
{
|
|
||||||
u32 data = 0;
|
|
||||||
|
|
||||||
if (config_access(PCI_ACCESS_READ, bus, devfn, where, &data))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
data = (data & ~(0xff << ((where & 3) << 3))) |
|
|
||||||
(val << ((where & 3) << 3));
|
|
||||||
|
|
||||||
if (config_access(PCI_ACCESS_WRITE, bus, devfn, where, &data))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return PCIBIOS_SUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_config_word(struct pci_bus *bus, unsigned int devfn, int where,
|
|
||||||
u16 val)
|
|
||||||
{
|
|
||||||
u32 data = 0;
|
|
||||||
|
|
||||||
if (config_access(PCI_ACCESS_READ, bus, devfn, where, &data))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
data = (data & ~(0xffff << ((where & 3) << 3))) |
|
|
||||||
(val << ((where & 3) << 3));
|
|
||||||
|
|
||||||
if (config_access(PCI_ACCESS_WRITE, bus, devfn, where, &data))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
|
|
||||||
return PCIBIOS_SUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
|
||||||
write_config_dword(struct pci_bus *bus, unsigned int devfn, int where,
|
|
||||||
u32 val)
|
|
||||||
{
|
|
||||||
if (config_access(PCI_ACCESS_WRITE, bus, devfn, where, &val))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
return PCIBIOS_SUCCESSFUL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pci_config_read(struct pci_bus *bus, unsigned int devfn,
|
|
||||||
int where, int size, u32 * val)
|
|
||||||
{
|
|
||||||
switch (size) {
|
|
||||||
case 1:
|
|
||||||
return read_config_byte(bus, devfn, where, (u8 *) val);
|
|
||||||
case 2:
|
|
||||||
return read_config_word(bus, devfn, where, (u16 *) val);
|
|
||||||
default:
|
|
||||||
return read_config_dword(bus, devfn, where, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int pci_config_write(struct pci_bus *bus, unsigned int devfn,
|
|
||||||
int where, int size, u32 val)
|
|
||||||
{
|
|
||||||
switch (size) {
|
|
||||||
case 1:
|
|
||||||
return write_config_byte(bus, devfn, where, (u8) val);
|
|
||||||
case 2:
|
|
||||||
return write_config_word(bus, devfn, where, (u16) val);
|
|
||||||
default:
|
|
||||||
return write_config_dword(bus, devfn, where, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct pci_ops rc32434_pci_ops = {
|
|
||||||
.read = pci_config_read,
|
|
||||||
.write = pci_config_write,
|
|
||||||
};
|
|
|
@ -1,236 +0,0 @@
|
||||||
/**************************************************************************
|
|
||||||
*
|
|
||||||
* BRIEF MODULE DESCRIPTION
|
|
||||||
* PCI initialization for IDT EB434 board
|
|
||||||
*
|
|
||||||
* Copyright 2004 IDT Inc. (rischelp@idt.com)
|
|
||||||
*
|
|
||||||
* 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
||||||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
||||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**************************************************************************
|
|
||||||
* May 2004 rkt, neb
|
|
||||||
*
|
|
||||||
* Initial Release
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
**************************************************************************
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/autoconf.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/pci.h>
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
|
|
||||||
#include <asm/rc32434/rc32434.h>
|
|
||||||
#include <asm/rc32434/pci.h>
|
|
||||||
|
|
||||||
#define PCI_ACCESS_READ 0
|
|
||||||
#define PCI_ACCESS_WRITE 1
|
|
||||||
|
|
||||||
/* define an unsigned array for the PCI registers */
|
|
||||||
unsigned int korinaCnfgRegs[25] = {
|
|
||||||
KORINA_CNFG1, KORINA_CNFG2, KORINA_CNFG3, KORINA_CNFG4,
|
|
||||||
KORINA_CNFG5, KORINA_CNFG6, KORINA_CNFG7, KORINA_CNFG8,
|
|
||||||
KORINA_CNFG9, KORINA_CNFG10, KORINA_CNFG11, KORINA_CNFG12,
|
|
||||||
KORINA_CNFG13, KORINA_CNFG14, KORINA_CNFG15, KORINA_CNFG16,
|
|
||||||
KORINA_CNFG17, KORINA_CNFG18, KORINA_CNFG19, KORINA_CNFG20,
|
|
||||||
KORINA_CNFG21, KORINA_CNFG22, KORINA_CNFG23, KORINA_CNFG24
|
|
||||||
};
|
|
||||||
static struct resource rc32434_res_pci_mem1;
|
|
||||||
static struct resource rc32434_res_pci_mem2;
|
|
||||||
|
|
||||||
static struct resource rc32434_res_pci_mem1 = {
|
|
||||||
.name = "PCI MEM1",
|
|
||||||
.start = 0x50000000,
|
|
||||||
.end = 0x5FFFFFFF,
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
.parent = &rc32434_res_pci_mem1,
|
|
||||||
.sibling = NULL,
|
|
||||||
.child = &rc32434_res_pci_mem2
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct resource rc32434_res_pci_mem2 = {
|
|
||||||
.name = "PCI Mem2",
|
|
||||||
.start = 0x60000000,
|
|
||||||
.end = 0x6FFFFFFF,
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
.parent = &rc32434_res_pci_mem1,
|
|
||||||
.sibling = NULL,
|
|
||||||
.child = NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct resource rc32434_res_pci_io1 = {
|
|
||||||
.name = "PCI I/O1",
|
|
||||||
.start = 0x18800000,
|
|
||||||
.end = 0x188FFFFF,
|
|
||||||
.flags = IORESOURCE_IO,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern struct pci_ops rc32434_pci_ops;
|
|
||||||
|
|
||||||
#define PCI_MEM1_START PCI_ADDR_START
|
|
||||||
#define PCI_MEM1_END PCI_ADDR_START + CPUTOPCI_MEM_WIN - 1
|
|
||||||
#define PCI_MEM2_START PCI_ADDR_START + CPUTOPCI_MEM_WIN
|
|
||||||
#define PCI_MEM2_END PCI_ADDR_START + ( 2* CPUTOPCI_MEM_WIN) - 1
|
|
||||||
#define PCI_IO1_START PCI_ADDR_START + (2 * CPUTOPCI_MEM_WIN)
|
|
||||||
#define PCI_IO1_END PCI_ADDR_START + (2* CPUTOPCI_MEM_WIN) + CPUTOPCI_IO_WIN -1
|
|
||||||
#define PCI_IO2_START PCI_ADDR_START + (2 * CPUTOPCI_MEM_WIN) + CPUTOPCI_IO_WIN
|
|
||||||
#define PCI_IO2_END PCI_ADDR_START + (2* CPUTOPCI_MEM_WIN) + (2 * CPUTOPCI_IO_WIN) -1
|
|
||||||
|
|
||||||
|
|
||||||
struct pci_controller rc32434_controller2;
|
|
||||||
|
|
||||||
struct pci_controller rc32434_controller = {
|
|
||||||
.pci_ops = &rc32434_pci_ops,
|
|
||||||
.mem_resource = &rc32434_res_pci_mem1,
|
|
||||||
.io_resource = &rc32434_res_pci_io1,
|
|
||||||
.mem_offset = 0,
|
|
||||||
.io_offset = 0,
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef __MIPSEB__
|
|
||||||
#define PCI_ENDIAN_FLAG PCILBAC_sb_m
|
|
||||||
#else
|
|
||||||
#define PCI_ENDIAN_FLAG 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int __init rc32434_pcibridge_init(void)
|
|
||||||
{
|
|
||||||
unsigned int pcicValue, pcicData = 0;
|
|
||||||
unsigned int dummyRead, pciCntlVal;
|
|
||||||
int loopCount;
|
|
||||||
unsigned int pciConfigAddr;
|
|
||||||
|
|
||||||
pcicValue = rc32434_pci->pcic;
|
|
||||||
pcicValue = (pcicValue >> PCIM_SHFT) & PCIM_BIT_LEN;
|
|
||||||
if (!((pcicValue == PCIM_H_EA) ||
|
|
||||||
(pcicValue == PCIM_H_IA_FIX) ||
|
|
||||||
(pcicValue == PCIM_H_IA_RR))) {
|
|
||||||
printk("PCI init error!!!\n");
|
|
||||||
/* Not in Host Mode, return ERROR */
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
/* Enables the Idle Grant mode, Arbiter Parking */
|
|
||||||
pcicData |=(PCIC_igm_m|PCIC_eap_m|PCIC_en_m);
|
|
||||||
rc32434_pci->pcic = pcicData; /* Enable the PCI bus Interface */
|
|
||||||
/* Zero out the PCI status & PCI Status Mask */
|
|
||||||
for(;;)
|
|
||||||
{
|
|
||||||
pcicData = rc32434_pci->pcis;
|
|
||||||
if (!(pcicData & PCIS_rip_m))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
rc32434_pci->pcis = 0;
|
|
||||||
rc32434_pci->pcism = 0xFFFFFFFF;
|
|
||||||
/* Zero out the PCI decoupled registers */
|
|
||||||
rc32434_pci->pcidac=0; /* disable PCI decoupled accesses at initialization */
|
|
||||||
rc32434_pci->pcidas=0; /* clear the status */
|
|
||||||
rc32434_pci->pcidasm=0x0000007F; /* Mask all the interrupts */
|
|
||||||
/* Mask PCI Messaging Interrupts */
|
|
||||||
rc32434_pci_msg->pciiic = 0;
|
|
||||||
rc32434_pci_msg->pciiim = 0xFFFFFFFF;
|
|
||||||
rc32434_pci_msg->pciioic = 0;
|
|
||||||
rc32434_pci_msg->pciioim = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/* Setup PCILB0 as Memory Window */
|
|
||||||
rc32434_pci->pcilba[0].a = (unsigned int) (PCI_ADDR_START);
|
|
||||||
|
|
||||||
/* setup the PCI map address as same as the local address */
|
|
||||||
|
|
||||||
rc32434_pci->pcilba[0].m = (unsigned int) (PCI_ADDR_START);
|
|
||||||
|
|
||||||
|
|
||||||
/* Setup PCILBA1 as MEM */
|
|
||||||
rc32434_pci->pcilba[0].c = ( ((SIZE_256MB & 0x1f) << PCILBAC_size_b) | PCI_ENDIAN_FLAG);
|
|
||||||
dummyRead = rc32434_pci->pcilba[0].c; /* flush the CPU write Buffers */
|
|
||||||
rc32434_pci->pcilba[1].a = 0x60000000;
|
|
||||||
rc32434_pci->pcilba[1].m = 0x60000000;
|
|
||||||
|
|
||||||
/* setup PCILBA2 as IO Window*/
|
|
||||||
rc32434_pci->pcilba[1].c = (((SIZE_256MB & 0x1f) << PCILBAC_size_b )| PCI_ENDIAN_FLAG);
|
|
||||||
dummyRead = rc32434_pci->pcilba[1].c; /* flush the CPU write Buffers */
|
|
||||||
rc32434_pci->pcilba[2].a = 0x18C00000;
|
|
||||||
rc32434_pci->pcilba[2].m = 0x18FFFFFF;
|
|
||||||
|
|
||||||
/* setup PCILBA2 as IO Window*/
|
|
||||||
rc32434_pci->pcilba[2].c = (((SIZE_4MB & 0x1f) << PCILBAC_size_b) | PCI_ENDIAN_FLAG );
|
|
||||||
dummyRead = rc32434_pci->pcilba[2].c; /* flush the CPU write Buffers */
|
|
||||||
|
|
||||||
/* Setup PCILBA3 as IO Window */
|
|
||||||
rc32434_pci->pcilba[3].a = 0x18800000;
|
|
||||||
rc32434_pci->pcilba[3].m = 0x18800000;
|
|
||||||
rc32434_pci->pcilba[3].c = ( (((SIZE_1MB & 0x1ff) << PCILBAC_size_b) | PCILBAC_msi_m) | PCI_ENDIAN_FLAG);
|
|
||||||
dummyRead = rc32434_pci->pcilba[3].c; /* flush the CPU write Buffers */
|
|
||||||
|
|
||||||
pciConfigAddr=(unsigned int)(0x80000004);
|
|
||||||
for(loopCount=0;loopCount<24;loopCount++){
|
|
||||||
rc32434_pci->pcicfga=pciConfigAddr;
|
|
||||||
dummyRead=rc32434_pci->pcicfga;
|
|
||||||
rc32434_pci->pcicfgd = korinaCnfgRegs[loopCount];
|
|
||||||
dummyRead=rc32434_pci->pcicfgd;
|
|
||||||
pciConfigAddr += 4;
|
|
||||||
}
|
|
||||||
rc32434_pci->pcitc = (unsigned int)((PCITC_RTIMER_VAL&0xff) << PCITC_rtimer_b)
|
|
||||||
| ((PCITC_DTIMER_VAL&0xff) << PCITC_dtimer_b);
|
|
||||||
|
|
||||||
pciCntlVal=rc32434_pci->pcic;
|
|
||||||
pciCntlVal &=~(PCIC_tnr_m);
|
|
||||||
rc32434_pci->pcic = pciCntlVal;
|
|
||||||
pciCntlVal=rc32434_pci->pcic;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Do platform specific device initialization at pci_enable_device() time */
|
|
||||||
int pcibios_plat_dev_init(struct pci_dev *dev)
|
|
||||||
{
|
|
||||||
if (PCI_SLOT(dev->devfn) == 6 && dev->bus->number == 0) {
|
|
||||||
/* disable prefetched memory range */
|
|
||||||
pci_write_config_word(dev, PCI_PREF_MEMORY_LIMIT, 0);
|
|
||||||
pci_write_config_word(dev, PCI_PREF_MEMORY_BASE, 0x10);
|
|
||||||
|
|
||||||
pci_write_config_byte(dev, PCI_CACHE_LINE_SIZE, 4);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __init rc32434_pci_init(void)
|
|
||||||
{
|
|
||||||
printk("PCI: Initializing PCI\n");
|
|
||||||
|
|
||||||
ioport_resource.start = rc32434_res_pci_io1.start;
|
|
||||||
ioport_resource.end = rc32434_res_pci_io1.end;
|
|
||||||
|
|
||||||
rc32434_pcibridge_init();
|
|
||||||
|
|
||||||
register_pci_controller(&rc32434_controller);
|
|
||||||
rc32434_sync();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
arch_initcall(rc32434_pci_init);
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
#
|
|
||||||
# Makefile for the RB500 board specific parts of the kernel
|
|
||||||
#
|
|
||||||
|
|
||||||
obj-y += irq.o time.o setup.o serial.o prom.o gpio.o devices.o
|
|
|
@ -1,338 +0,0 @@
|
||||||
/*
|
|
||||||
* RouterBoard 500 Platform devices
|
|
||||||
*
|
|
||||||
* Copyright (C) 2006 Felix Fietkau <nbd@openwrt.org>
|
|
||||||
* Copyright (C) 2007 Florian Fainelli <florian@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/kernel.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/ctype.h>
|
|
||||||
#include <linux/string.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/mtd/nand.h>
|
|
||||||
#include <linux/mtd/mtd.h>
|
|
||||||
#include <linux/mtd/partitions.h>
|
|
||||||
#include <linux/gpio_keys.h>
|
|
||||||
#include <linux/input.h>
|
|
||||||
|
|
||||||
#include <asm/bootinfo.h>
|
|
||||||
|
|
||||||
#include <asm/rc32434/rc32434.h>
|
|
||||||
#include <asm/rc32434/dma.h>
|
|
||||||
#include <asm/rc32434/dma_v.h>
|
|
||||||
#include <asm/rc32434/eth.h>
|
|
||||||
#include <asm/rc32434/rb.h>
|
|
||||||
|
|
||||||
#define ETH0_DMA_RX_IRQ GROUP1_IRQ_BASE + 0
|
|
||||||
#define ETH0_DMA_TX_IRQ GROUP1_IRQ_BASE + 1
|
|
||||||
#define ETH0_RX_OVR_IRQ GROUP3_IRQ_BASE + 9
|
|
||||||
#define ETH0_TX_UND_IRQ GROUP3_IRQ_BASE + 10
|
|
||||||
|
|
||||||
#define ETH0_RX_DMA_ADDR (DMA0_PhysicalAddress + 0*DMA_CHAN_OFFSET)
|
|
||||||
#define ETH0_TX_DMA_ADDR (DMA0_PhysicalAddress + 1*DMA_CHAN_OFFSET)
|
|
||||||
|
|
||||||
/* NAND definitions */
|
|
||||||
#define MEM32(x) *((volatile unsigned *) (x))
|
|
||||||
|
|
||||||
#define GPIO_RDY (1 << 0x08)
|
|
||||||
#define GPIO_WPX (1 << 0x09)
|
|
||||||
#define GPIO_ALE (1 << 0x0a)
|
|
||||||
#define GPIO_CLE (1 << 0x0b)
|
|
||||||
|
|
||||||
extern char* board_type;
|
|
||||||
|
|
||||||
static struct resource korina_dev0_res[] = {
|
|
||||||
{
|
|
||||||
.name = "korina_regs",
|
|
||||||
.start = ETH0_PhysicalAddress,
|
|
||||||
.end = ETH0_PhysicalAddress + sizeof(ETH_t),
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
}, {
|
|
||||||
.name = "korina_rx",
|
|
||||||
.start = ETH0_DMA_RX_IRQ,
|
|
||||||
.end = ETH0_DMA_RX_IRQ,
|
|
||||||
.flags = IORESOURCE_IRQ
|
|
||||||
}, {
|
|
||||||
.name = "korina_tx",
|
|
||||||
.start = ETH0_DMA_TX_IRQ,
|
|
||||||
.end = ETH0_DMA_TX_IRQ,
|
|
||||||
.flags = IORESOURCE_IRQ
|
|
||||||
}, {
|
|
||||||
.name = "korina_ovr",
|
|
||||||
.start = ETH0_RX_OVR_IRQ,
|
|
||||||
.end = ETH0_RX_OVR_IRQ,
|
|
||||||
.flags = IORESOURCE_IRQ
|
|
||||||
}, {
|
|
||||||
.name = "korina_und",
|
|
||||||
.start = ETH0_TX_UND_IRQ,
|
|
||||||
.end = ETH0_TX_UND_IRQ,
|
|
||||||
.flags = IORESOURCE_IRQ
|
|
||||||
}, {
|
|
||||||
.name = "korina_dma_rx",
|
|
||||||
.start = ETH0_RX_DMA_ADDR,
|
|
||||||
.end = ETH0_RX_DMA_ADDR + DMA_CHAN_OFFSET - 1,
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
}, {
|
|
||||||
.name = "korina_dma_tx",
|
|
||||||
.start = ETH0_TX_DMA_ADDR,
|
|
||||||
.end = ETH0_TX_DMA_ADDR + DMA_CHAN_OFFSET - 1,
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct korina_device korina_dev0_data = {
|
|
||||||
.name = "korina0",
|
|
||||||
.mac = {0xde, 0xca, 0xff, 0xc0, 0xff, 0xee}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device korina_dev0 = {
|
|
||||||
.id = 0,
|
|
||||||
.name = "korina",
|
|
||||||
.dev.platform_data = &korina_dev0_data,
|
|
||||||
.resource = korina_dev0_res,
|
|
||||||
.num_resources = ARRAY_SIZE(korina_dev0_res),
|
|
||||||
};
|
|
||||||
|
|
||||||
#define CF_GPIO_NUM 13
|
|
||||||
|
|
||||||
static struct resource cf_slot0_res[] = {
|
|
||||||
{
|
|
||||||
.name = "cf_membase",
|
|
||||||
.flags = IORESOURCE_MEM
|
|
||||||
}, {
|
|
||||||
.name = "cf_irq",
|
|
||||||
.start = (8 + 4 * 32 + CF_GPIO_NUM), /* 149 */
|
|
||||||
.end = (8 + 4 * 32 + CF_GPIO_NUM),
|
|
||||||
.flags = IORESOURCE_IRQ
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct cf_device cf_slot0_data = {
|
|
||||||
.gpio_pin = 13
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device cf_slot0 = {
|
|
||||||
.id = 0,
|
|
||||||
.name = "rb500-cf",
|
|
||||||
.dev.platform_data = &cf_slot0_data,
|
|
||||||
.resource = cf_slot0_res,
|
|
||||||
.num_resources = ARRAY_SIZE(cf_slot0_res),
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Resources and device for NAND. There is no data needed and no irqs, so just define the memory used. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We need to use the OLD Yaffs-1 OOB layout, otherwise the RB bootloader
|
|
||||||
* will not be able to find the kernel that we load. So set the oobinfo
|
|
||||||
* when creating the partitions
|
|
||||||
*/
|
|
||||||
static struct nand_ecclayout rb500_nand_ecclayout = {
|
|
||||||
.eccbytes = 6,
|
|
||||||
.eccpos = { 8, 9, 10, 13, 14, 15 },
|
|
||||||
.oobavail = 9,
|
|
||||||
.oobfree = { { 0, 4 }, { 6, 2 }, { 11, 2 }, { 4, 1 } }
|
|
||||||
};
|
|
||||||
|
|
||||||
int rb500_dev_ready(struct mtd_info *mtd)
|
|
||||||
{
|
|
||||||
return MEM32(IDT434_REG_BASE + GPIOD) & GPIO_RDY;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rb500_cmd_ctrl(struct mtd_info *mtd, int cmd, unsigned int ctrl)
|
|
||||||
{
|
|
||||||
struct nand_chip *chip = mtd->priv;
|
|
||||||
unsigned char orbits, nandbits;
|
|
||||||
|
|
||||||
if (ctrl & NAND_CTRL_CHANGE) {
|
|
||||||
|
|
||||||
orbits = (ctrl & NAND_CLE) << 1;
|
|
||||||
orbits |= (ctrl & NAND_ALE) >> 1;
|
|
||||||
|
|
||||||
nandbits = (~ctrl & NAND_CLE) << 1;
|
|
||||||
nandbits |= (~ctrl & NAND_ALE) >> 1;
|
|
||||||
|
|
||||||
changeLatchU5(orbits, nandbits);
|
|
||||||
}
|
|
||||||
if (cmd != NAND_CMD_NONE)
|
|
||||||
writeb(cmd, chip->IO_ADDR_W);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct resource nand_slot0_res[] = {
|
|
||||||
[0] = {
|
|
||||||
.name = "nand_membase",
|
|
||||||
.flags = IORESOURCE_MEM
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct platform_nand_data rb500_nand_data = {
|
|
||||||
.ctrl.dev_ready = rb500_dev_ready,
|
|
||||||
.ctrl.cmd_ctrl = rb500_cmd_ctrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device nand_slot0 = {
|
|
||||||
.name = "gen_nand",
|
|
||||||
.id = -1,
|
|
||||||
.resource = nand_slot0_res,
|
|
||||||
.num_resources = ARRAY_SIZE(nand_slot0_res),
|
|
||||||
.dev.platform_data = &rb500_nand_data,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct mtd_partition rb500_partition_info[] = {
|
|
||||||
{
|
|
||||||
.name = "Routerboard NAND boot",
|
|
||||||
.offset = 0,
|
|
||||||
.size = 4 * 1024 * 1024,
|
|
||||||
}, {
|
|
||||||
.name = "rootfs",
|
|
||||||
.offset = MTDPART_OFS_NXTBLK,
|
|
||||||
.size = MTDPART_SIZ_FULL,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device rb500_led = {
|
|
||||||
.name = "rb500-led",
|
|
||||||
.id = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct gpio_keys_button rb500_gpio_btn[] = {
|
|
||||||
{
|
|
||||||
.gpio = 1,
|
|
||||||
.code = BTN_0,
|
|
||||||
.desc = "S1",
|
|
||||||
.active_low = 1,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct gpio_keys_platform_data rb500_gpio_btn_data = {
|
|
||||||
.buttons = rb500_gpio_btn,
|
|
||||||
.nbuttons = ARRAY_SIZE(rb500_gpio_btn),
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device rb500_button = {
|
|
||||||
.name = "gpio-keys",
|
|
||||||
.id = -1,
|
|
||||||
.dev = {
|
|
||||||
.platform_data = &rb500_gpio_btn_data,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct platform_device *rb500_devs[] = {
|
|
||||||
&korina_dev0,
|
|
||||||
&nand_slot0,
|
|
||||||
&cf_slot0,
|
|
||||||
&rb500_led,
|
|
||||||
&rb500_button
|
|
||||||
};
|
|
||||||
|
|
||||||
static void __init parse_mac_addr(char *macstr)
|
|
||||||
{
|
|
||||||
int i, j;
|
|
||||||
unsigned char result, value;
|
|
||||||
|
|
||||||
for (i = 0; i < 6; i++) {
|
|
||||||
result = 0;
|
|
||||||
|
|
||||||
if (i != 5 && *(macstr + 2) != ':')
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (j = 0; j < 2; j++) {
|
|
||||||
if (isxdigit(*macstr)
|
|
||||||
&& (value =
|
|
||||||
isdigit(*macstr) ? *macstr -
|
|
||||||
'0' : toupper(*macstr) - 'A' + 10) < 16) {
|
|
||||||
result = result * 16 + value;
|
|
||||||
macstr++;
|
|
||||||
} else
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
macstr++;
|
|
||||||
korina_dev0_data.mac[i] = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* DEVICE CONTROLLER 1 */
|
|
||||||
#define CFG_DC_DEV1 (void*)0xb8010010
|
|
||||||
#define CFG_DC_DEV2 (void*)0xb8010020
|
|
||||||
#define CFG_DC_DEVBASE 0x0
|
|
||||||
#define CFG_DC_DEVMASK 0x4
|
|
||||||
#define CFG_DC_DEVC 0x8
|
|
||||||
#define CFG_DC_DEVTC 0xC
|
|
||||||
|
|
||||||
/* NAND definitions */
|
|
||||||
#define NAND_CHIP_DELAY 25
|
|
||||||
|
|
||||||
static int rb500_nand_fixup(struct mtd_info *mtd)
|
|
||||||
{
|
|
||||||
struct nand_chip *chip = mtd->priv;
|
|
||||||
|
|
||||||
if (mtd->writesize == 512)
|
|
||||||
chip->ecc.layout = &rb500_nand_ecclayout;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __init rb500_nand_setup(void)
|
|
||||||
{
|
|
||||||
switch (mips_machtype) {
|
|
||||||
case MACH_MIKROTIK_RB532A:
|
|
||||||
changeLatchU5(LO_FOFF | LO_CEX, LO_ULED | LO_ALE | LO_CLE | LO_WPX);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
changeLatchU5(LO_WPX | LO_FOFF | LO_CEX, LO_ULED | LO_ALE | LO_CLE);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Setup NAND specific settings */
|
|
||||||
rb500_nand_data.chip.nr_chips = 1;
|
|
||||||
rb500_nand_data.chip.nr_partitions = ARRAY_SIZE(rb500_partition_info);
|
|
||||||
rb500_nand_data.chip.partitions = rb500_partition_info;
|
|
||||||
rb500_nand_data.chip.chip_delay = NAND_CHIP_DELAY;
|
|
||||||
rb500_nand_data.chip.options = NAND_NO_AUTOINCR;
|
|
||||||
|
|
||||||
rb500_nand_data.chip.chip_fixup = &rb500_nand_fixup;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int __init plat_setup_devices(void)
|
|
||||||
{
|
|
||||||
/* Look for the CF card reader */
|
|
||||||
if (!readl(CFG_DC_DEV1 + CFG_DC_DEVMASK))
|
|
||||||
rb500_devs[1] = NULL;
|
|
||||||
else {
|
|
||||||
cf_slot0_res[0].start =
|
|
||||||
readl(CFG_DC_DEV1 + CFG_DC_DEVBASE);
|
|
||||||
cf_slot0_res[0].end = cf_slot0_res[0].start + 0x1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read the NAND resources from the device controller */
|
|
||||||
nand_slot0_res[0].start = readl(CFG_DC_DEV2 + CFG_DC_DEVBASE);
|
|
||||||
nand_slot0_res[0].end = nand_slot0_res[0].start + 0x1000;
|
|
||||||
|
|
||||||
/* Initialise the NAND device */
|
|
||||||
rb500_nand_setup();
|
|
||||||
|
|
||||||
return platform_add_devices(rb500_devs, ARRAY_SIZE(rb500_devs));
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __init setup_kmac(char *s)
|
|
||||||
{
|
|
||||||
printk("korina mac = %s\n", s);
|
|
||||||
parse_mac_addr(s);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
__setup("kmac=", setup_kmac);
|
|
||||||
|
|
||||||
arch_initcall(plat_setup_devices);
|
|
|
@ -1,198 +0,0 @@
|
||||||
/*
|
|
||||||
* Miscellaneous functions for IDT EB434 board
|
|
||||||
*
|
|
||||||
* Copyright 2004 IDT Inc. (rischelp@idt.com)
|
|
||||||
* Copyright 2006 Phil Sutter <n0-1@freewrt.org>
|
|
||||||
* Copyright 2007 Florian Fainelli <florian@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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
||||||
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
|
||||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/kernel.h>
|
|
||||||
#include <linux/init.h>
|
|
||||||
#include <linux/types.h>
|
|
||||||
#include <linux/pci.h>
|
|
||||||
#include <linux/spinlock.h>
|
|
||||||
#include <linux/io.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
|
|
||||||
#include <asm/addrspace.h>
|
|
||||||
#include <asm/gpio.h>
|
|
||||||
|
|
||||||
#include <asm/rc32434/rb.h>
|
|
||||||
|
|
||||||
#define GPIO_BADDR 0x18050000
|
|
||||||
|
|
||||||
static volatile unsigned char *devCtl3Base;
|
|
||||||
static unsigned char latchU5State;
|
|
||||||
static spinlock_t clu5Lock = SPIN_LOCK_UNLOCKED;
|
|
||||||
|
|
||||||
struct rb500_gpio_reg __iomem *rb500_gpio_reg0;
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_reg0);
|
|
||||||
|
|
||||||
static struct resource rb500_gpio_reg0_res[] = {
|
|
||||||
{
|
|
||||||
.name = "gpio_reg0",
|
|
||||||
.start = GPIO_BADDR,
|
|
||||||
.end = GPIO_BADDR + sizeof(struct rb500_gpio_reg),
|
|
||||||
.flags = IORESOURCE_MEM,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void set434Reg(unsigned regOffs, unsigned bit, unsigned len, unsigned val)
|
|
||||||
{
|
|
||||||
unsigned flags, data;
|
|
||||||
unsigned i = 0;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&clu5Lock, flags);
|
|
||||||
data = *(volatile unsigned *) (IDT434_REG_BASE + regOffs);
|
|
||||||
for (i = 0; i != len; ++i) {
|
|
||||||
if (val & (1 << i))
|
|
||||||
data |= (1 << (i + bit));
|
|
||||||
else
|
|
||||||
data &= ~(1 << (i + bit));
|
|
||||||
}
|
|
||||||
*(volatile unsigned *) (IDT434_REG_BASE + regOffs) = data;
|
|
||||||
spin_unlock_irqrestore(&clu5Lock, flags);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(set434Reg);
|
|
||||||
|
|
||||||
void changeLatchU5(unsigned char orMask, unsigned char nandMask)
|
|
||||||
{
|
|
||||||
unsigned flags;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&clu5Lock, flags);
|
|
||||||
latchU5State = (latchU5State | orMask) & ~nandMask;
|
|
||||||
if (!devCtl3Base)
|
|
||||||
devCtl3Base = (volatile unsigned char *)
|
|
||||||
KSEG1ADDR(*(volatile unsigned *)
|
|
||||||
KSEG1ADDR(0x18010030));
|
|
||||||
*devCtl3Base = latchU5State;
|
|
||||||
spin_unlock_irqrestore(&clu5Lock, flags);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(changeLatchU5);
|
|
||||||
|
|
||||||
unsigned char getLatchU5State(void)
|
|
||||||
{
|
|
||||||
return latchU5State;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(getLatchU5State);
|
|
||||||
|
|
||||||
int rb500_gpio_get_value(unsigned gpio)
|
|
||||||
{
|
|
||||||
return readl(&rb500_gpio_reg0->gpiod) & (1 << gpio);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_get_value);
|
|
||||||
|
|
||||||
void rb500_gpio_set_value(unsigned gpio, int value)
|
|
||||||
{
|
|
||||||
unsigned tmp;
|
|
||||||
|
|
||||||
tmp = readl(&rb500_gpio_reg0->gpiod) & ~(1 << gpio);
|
|
||||||
if (value)
|
|
||||||
tmp |= 1 << gpio;
|
|
||||||
|
|
||||||
writel(tmp, (void *)&rb500_gpio_reg0->gpiod);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_set_value);
|
|
||||||
|
|
||||||
int rb500_gpio_direction_input(unsigned gpio)
|
|
||||||
{
|
|
||||||
writel(readl(&rb500_gpio_reg0->gpiocfg) & ~(1 << gpio), (void *)&rb500_gpio_reg0->gpiocfg);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_direction_input);
|
|
||||||
|
|
||||||
int rb500_gpio_direction_output(unsigned gpio, int value)
|
|
||||||
{
|
|
||||||
gpio_set_value(gpio, value);
|
|
||||||
writel(readl(&rb500_gpio_reg0->gpiocfg) | (1 << gpio), (void *)&rb500_gpio_reg0->gpiocfg);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_direction_output);
|
|
||||||
|
|
||||||
void rb500_gpio_set_int_level(unsigned gpio, int value)
|
|
||||||
{
|
|
||||||
unsigned tmp;
|
|
||||||
|
|
||||||
tmp = readl(&rb500_gpio_reg0->gpioilevel) & ~(1 << gpio);
|
|
||||||
if (value)
|
|
||||||
tmp |= 1 << gpio;
|
|
||||||
writel(tmp, (void *)&rb500_gpio_reg0->gpioilevel);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_set_int_level);
|
|
||||||
|
|
||||||
int rb500_gpio_get_int_level(unsigned gpio)
|
|
||||||
{
|
|
||||||
return readl(&rb500_gpio_reg0->gpioilevel) & (1 << gpio);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_get_int_level);
|
|
||||||
|
|
||||||
void rb500_gpio_set_int_status(unsigned gpio, int value)
|
|
||||||
{
|
|
||||||
unsigned tmp;
|
|
||||||
|
|
||||||
tmp = readl(&rb500_gpio_reg0->gpioistat);
|
|
||||||
if (value)
|
|
||||||
tmp |= 1 << gpio;
|
|
||||||
writel(tmp, (void *)&rb500_gpio_reg0->gpioistat);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_set_int_status);
|
|
||||||
|
|
||||||
int rb500_gpio_get_int_status(unsigned gpio)
|
|
||||||
{
|
|
||||||
return readl(&rb500_gpio_reg0->gpioistat) & (1 << gpio);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_get_int_status);
|
|
||||||
|
|
||||||
void rb500_gpio_set_func(unsigned gpio, int value)
|
|
||||||
{
|
|
||||||
unsigned tmp;
|
|
||||||
|
|
||||||
tmp = readl(&rb500_gpio_reg0->gpiofunc);
|
|
||||||
if (value)
|
|
||||||
tmp |= 1 << gpio;
|
|
||||||
writel(tmp, (void *)&rb500_gpio_reg0->gpiofunc);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_set_func);
|
|
||||||
|
|
||||||
int rb500_gpio_get_func(unsigned gpio)
|
|
||||||
{
|
|
||||||
return readl(&rb500_gpio_reg0->gpiofunc) & (1 << gpio);
|
|
||||||
}
|
|
||||||
EXPORT_SYMBOL(rb500_gpio_get_func);
|
|
||||||
|
|
||||||
int __init rb500_gpio_init(void)
|
|
||||||
{
|
|
||||||
rb500_gpio_reg0 = ioremap_nocache(rb500_gpio_reg0_res[0].start,
|
|
||||||
rb500_gpio_reg0_res[0].end -
|
|
||||||
rb500_gpio_reg0_res[0].start);
|
|
||||||
|
|
||||||
if (!rb500_gpio_reg0) {
|
|
||||||
printk(KERN_ERR "rb500: cannot remap GPIO register 0\n");
|
|
||||||
return -ENXIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
arch_initcall(rb500_gpio_init);
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue