forked from Minki/linux
58c11b5fae
NetCP on Keystone has cpsw ale function similar to other TI SoCs and this driver is re-used. To allow both ti cpsw and keystone netcp to re-use the driver, convert the cpsw ale to a module and configure it through Kconfig option CONFIG_TI_CPSW_ALE. Currently it is statically linked to both TI CPSW and NetCP and this causes issues when the above drivers are built as dynamic modules. This patch addresses this issue While at it, fix the Makefile and code to build both netcp_core and netcp_ethss as dynamic modules. This is needed to support arm allmodconfig. This also requires exporting of API calls provided by netcp_core so that both the above can be dynamic modules. Signed-off-by: Murali Karicheri <m-karicheri2@ti.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Acked-by: Lad, Prabhakar <prabhakar.csengg@gmail.com> Acked-by: Mugunthan V N <mugunthanvnm@ti.com> Tested-by: Mugunthan V N <mugunthanvnm@ti.com> Signed-off-by: David S. Miller <davem@davemloft.net>
832 lines
19 KiB
C
832 lines
19 KiB
C
/*
|
|
* Texas Instruments 3-Port Ethernet Switch Address Lookup Engine
|
|
*
|
|
* Copyright (C) 2012 Texas Instruments
|
|
*
|
|
* 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 version 2.
|
|
*
|
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
* kind, whether express or implied; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include "cpsw_ale.h"
|
|
|
|
#define BITMASK(bits) (BIT(bits) - 1)
|
|
|
|
#define ALE_VERSION_MAJOR(rev) ((rev >> 8) & 0xff)
|
|
#define ALE_VERSION_MINOR(rev) (rev & 0xff)
|
|
|
|
/* ALE Registers */
|
|
#define ALE_IDVER 0x00
|
|
#define ALE_CONTROL 0x08
|
|
#define ALE_PRESCALE 0x10
|
|
#define ALE_UNKNOWNVLAN 0x18
|
|
#define ALE_TABLE_CONTROL 0x20
|
|
#define ALE_TABLE 0x34
|
|
#define ALE_PORTCTL 0x40
|
|
|
|
#define ALE_TABLE_WRITE BIT(31)
|
|
|
|
#define ALE_TYPE_FREE 0
|
|
#define ALE_TYPE_ADDR 1
|
|
#define ALE_TYPE_VLAN 2
|
|
#define ALE_TYPE_VLAN_ADDR 3
|
|
|
|
#define ALE_UCAST_PERSISTANT 0
|
|
#define ALE_UCAST_UNTOUCHED 1
|
|
#define ALE_UCAST_OUI 2
|
|
#define ALE_UCAST_TOUCHED 3
|
|
|
|
static inline int cpsw_ale_get_field(u32 *ale_entry, u32 start, u32 bits)
|
|
{
|
|
int idx;
|
|
|
|
idx = start / 32;
|
|
start -= idx * 32;
|
|
idx = 2 - idx; /* flip */
|
|
return (ale_entry[idx] >> start) & BITMASK(bits);
|
|
}
|
|
|
|
static inline void cpsw_ale_set_field(u32 *ale_entry, u32 start, u32 bits,
|
|
u32 value)
|
|
{
|
|
int idx;
|
|
|
|
value &= BITMASK(bits);
|
|
idx = start / 32;
|
|
start -= idx * 32;
|
|
idx = 2 - idx; /* flip */
|
|
ale_entry[idx] &= ~(BITMASK(bits) << start);
|
|
ale_entry[idx] |= (value << start);
|
|
}
|
|
|
|
#define DEFINE_ALE_FIELD(name, start, bits) \
|
|
static inline int cpsw_ale_get_##name(u32 *ale_entry) \
|
|
{ \
|
|
return cpsw_ale_get_field(ale_entry, start, bits); \
|
|
} \
|
|
static inline void cpsw_ale_set_##name(u32 *ale_entry, u32 value) \
|
|
{ \
|
|
cpsw_ale_set_field(ale_entry, start, bits, value); \
|
|
}
|
|
|
|
DEFINE_ALE_FIELD(entry_type, 60, 2)
|
|
DEFINE_ALE_FIELD(vlan_id, 48, 12)
|
|
DEFINE_ALE_FIELD(mcast_state, 62, 2)
|
|
DEFINE_ALE_FIELD(port_mask, 66, 3)
|
|
DEFINE_ALE_FIELD(super, 65, 1)
|
|
DEFINE_ALE_FIELD(ucast_type, 62, 2)
|
|
DEFINE_ALE_FIELD(port_num, 66, 2)
|
|
DEFINE_ALE_FIELD(blocked, 65, 1)
|
|
DEFINE_ALE_FIELD(secure, 64, 1)
|
|
DEFINE_ALE_FIELD(vlan_untag_force, 24, 3)
|
|
DEFINE_ALE_FIELD(vlan_reg_mcast, 16, 3)
|
|
DEFINE_ALE_FIELD(vlan_unreg_mcast, 8, 3)
|
|
DEFINE_ALE_FIELD(vlan_member_list, 0, 3)
|
|
DEFINE_ALE_FIELD(mcast, 40, 1)
|
|
|
|
/* The MAC address field in the ALE entry cannot be macroized as above */
|
|
static inline void cpsw_ale_get_addr(u32 *ale_entry, u8 *addr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 6; i++)
|
|
addr[i] = cpsw_ale_get_field(ale_entry, 40 - 8*i, 8);
|
|
}
|
|
|
|
static inline void cpsw_ale_set_addr(u32 *ale_entry, u8 *addr)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 6; i++)
|
|
cpsw_ale_set_field(ale_entry, 40 - 8*i, 8, addr[i]);
|
|
}
|
|
|
|
static int cpsw_ale_read(struct cpsw_ale *ale, int idx, u32 *ale_entry)
|
|
{
|
|
int i;
|
|
|
|
WARN_ON(idx > ale->params.ale_entries);
|
|
|
|
__raw_writel(idx, ale->params.ale_regs + ALE_TABLE_CONTROL);
|
|
|
|
for (i = 0; i < ALE_ENTRY_WORDS; i++)
|
|
ale_entry[i] = __raw_readl(ale->params.ale_regs +
|
|
ALE_TABLE + 4 * i);
|
|
|
|
return idx;
|
|
}
|
|
|
|
static int cpsw_ale_write(struct cpsw_ale *ale, int idx, u32 *ale_entry)
|
|
{
|
|
int i;
|
|
|
|
WARN_ON(idx > ale->params.ale_entries);
|
|
|
|
for (i = 0; i < ALE_ENTRY_WORDS; i++)
|
|
__raw_writel(ale_entry[i], ale->params.ale_regs +
|
|
ALE_TABLE + 4 * i);
|
|
|
|
__raw_writel(idx | ALE_TABLE_WRITE, ale->params.ale_regs +
|
|
ALE_TABLE_CONTROL);
|
|
|
|
return idx;
|
|
}
|
|
|
|
static int cpsw_ale_match_addr(struct cpsw_ale *ale, u8 *addr, u16 vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int type, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
u8 entry_addr[6];
|
|
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
type = cpsw_ale_get_entry_type(ale_entry);
|
|
if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR)
|
|
continue;
|
|
if (cpsw_ale_get_vlan_id(ale_entry) != vid)
|
|
continue;
|
|
cpsw_ale_get_addr(ale_entry, entry_addr);
|
|
if (ether_addr_equal(entry_addr, addr))
|
|
return idx;
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int cpsw_ale_match_vlan(struct cpsw_ale *ale, u16 vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int type, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
type = cpsw_ale_get_entry_type(ale_entry);
|
|
if (type != ALE_TYPE_VLAN)
|
|
continue;
|
|
if (cpsw_ale_get_vlan_id(ale_entry) == vid)
|
|
return idx;
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int cpsw_ale_match_free(struct cpsw_ale *ale)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int type, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
type = cpsw_ale_get_entry_type(ale_entry);
|
|
if (type == ALE_TYPE_FREE)
|
|
return idx;
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int cpsw_ale_find_ageable(struct cpsw_ale *ale)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int type, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
type = cpsw_ale_get_entry_type(ale_entry);
|
|
if (type != ALE_TYPE_ADDR && type != ALE_TYPE_VLAN_ADDR)
|
|
continue;
|
|
if (cpsw_ale_get_mcast(ale_entry))
|
|
continue;
|
|
type = cpsw_ale_get_ucast_type(ale_entry);
|
|
if (type != ALE_UCAST_PERSISTANT &&
|
|
type != ALE_UCAST_OUI)
|
|
return idx;
|
|
}
|
|
return -ENOENT;
|
|
}
|
|
|
|
static void cpsw_ale_flush_mcast(struct cpsw_ale *ale, u32 *ale_entry,
|
|
int port_mask)
|
|
{
|
|
int mask;
|
|
|
|
mask = cpsw_ale_get_port_mask(ale_entry);
|
|
if ((mask & port_mask) == 0)
|
|
return; /* ports dont intersect, not interested */
|
|
mask &= ~port_mask;
|
|
|
|
/* free if only remaining port is host port */
|
|
if (mask)
|
|
cpsw_ale_set_port_mask(ale_entry, mask);
|
|
else
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
|
}
|
|
|
|
int cpsw_ale_flush_multicast(struct cpsw_ale *ale, int port_mask, int vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int ret, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
ret = cpsw_ale_get_entry_type(ale_entry);
|
|
if (ret != ALE_TYPE_ADDR && ret != ALE_TYPE_VLAN_ADDR)
|
|
continue;
|
|
|
|
/* if vid passed is -1 then remove all multicast entry from
|
|
* the table irrespective of vlan id, if a valid vlan id is
|
|
* passed then remove only multicast added to that vlan id.
|
|
* if vlan id doesn't match then move on to next entry.
|
|
*/
|
|
if (vid != -1 && cpsw_ale_get_vlan_id(ale_entry) != vid)
|
|
continue;
|
|
|
|
if (cpsw_ale_get_mcast(ale_entry)) {
|
|
u8 addr[6];
|
|
|
|
cpsw_ale_get_addr(ale_entry, addr);
|
|
if (!is_broadcast_ether_addr(addr))
|
|
cpsw_ale_flush_mcast(ale, ale_entry, port_mask);
|
|
}
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_flush_multicast);
|
|
|
|
static void cpsw_ale_flush_ucast(struct cpsw_ale *ale, u32 *ale_entry,
|
|
int port_mask)
|
|
{
|
|
int port;
|
|
|
|
port = cpsw_ale_get_port_num(ale_entry);
|
|
if ((BIT(port) & port_mask) == 0)
|
|
return; /* ports dont intersect, not interested */
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
|
}
|
|
|
|
int cpsw_ale_flush(struct cpsw_ale *ale, int port_mask)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int ret, idx;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
ret = cpsw_ale_get_entry_type(ale_entry);
|
|
if (ret != ALE_TYPE_ADDR && ret != ALE_TYPE_VLAN_ADDR)
|
|
continue;
|
|
|
|
if (cpsw_ale_get_mcast(ale_entry))
|
|
cpsw_ale_flush_mcast(ale, ale_entry, port_mask);
|
|
else
|
|
cpsw_ale_flush_ucast(ale, ale_entry, port_mask);
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_flush);
|
|
|
|
static inline void cpsw_ale_set_vlan_entry_type(u32 *ale_entry,
|
|
int flags, u16 vid)
|
|
{
|
|
if (flags & ALE_VLAN) {
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_VLAN_ADDR);
|
|
cpsw_ale_set_vlan_id(ale_entry, vid);
|
|
} else {
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_ADDR);
|
|
}
|
|
}
|
|
|
|
int cpsw_ale_add_ucast(struct cpsw_ale *ale, u8 *addr, int port,
|
|
int flags, u16 vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx;
|
|
|
|
cpsw_ale_set_vlan_entry_type(ale_entry, flags, vid);
|
|
|
|
cpsw_ale_set_addr(ale_entry, addr);
|
|
cpsw_ale_set_ucast_type(ale_entry, ALE_UCAST_PERSISTANT);
|
|
cpsw_ale_set_secure(ale_entry, (flags & ALE_SECURE) ? 1 : 0);
|
|
cpsw_ale_set_blocked(ale_entry, (flags & ALE_BLOCKED) ? 1 : 0);
|
|
cpsw_ale_set_port_num(ale_entry, port);
|
|
|
|
idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0);
|
|
if (idx < 0)
|
|
idx = cpsw_ale_match_free(ale);
|
|
if (idx < 0)
|
|
idx = cpsw_ale_find_ageable(ale);
|
|
if (idx < 0)
|
|
return -ENOMEM;
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_add_ucast);
|
|
|
|
int cpsw_ale_del_ucast(struct cpsw_ale *ale, u8 *addr, int port,
|
|
int flags, u16 vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx;
|
|
|
|
idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0);
|
|
if (idx < 0)
|
|
return -ENOENT;
|
|
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_del_ucast);
|
|
|
|
int cpsw_ale_add_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask,
|
|
int flags, u16 vid, int mcast_state)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx, mask;
|
|
|
|
idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0);
|
|
if (idx >= 0)
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
|
|
cpsw_ale_set_vlan_entry_type(ale_entry, flags, vid);
|
|
|
|
cpsw_ale_set_addr(ale_entry, addr);
|
|
cpsw_ale_set_super(ale_entry, (flags & ALE_BLOCKED) ? 1 : 0);
|
|
cpsw_ale_set_mcast_state(ale_entry, mcast_state);
|
|
|
|
mask = cpsw_ale_get_port_mask(ale_entry);
|
|
port_mask |= mask;
|
|
cpsw_ale_set_port_mask(ale_entry, port_mask);
|
|
|
|
if (idx < 0)
|
|
idx = cpsw_ale_match_free(ale);
|
|
if (idx < 0)
|
|
idx = cpsw_ale_find_ageable(ale);
|
|
if (idx < 0)
|
|
return -ENOMEM;
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_add_mcast);
|
|
|
|
int cpsw_ale_del_mcast(struct cpsw_ale *ale, u8 *addr, int port_mask,
|
|
int flags, u16 vid)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx;
|
|
|
|
idx = cpsw_ale_match_addr(ale, addr, (flags & ALE_VLAN) ? vid : 0);
|
|
if (idx < 0)
|
|
return -EINVAL;
|
|
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
|
|
if (port_mask)
|
|
cpsw_ale_set_port_mask(ale_entry, port_mask);
|
|
else
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_del_mcast);
|
|
|
|
int cpsw_ale_add_vlan(struct cpsw_ale *ale, u16 vid, int port, int untag,
|
|
int reg_mcast, int unreg_mcast)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx;
|
|
|
|
idx = cpsw_ale_match_vlan(ale, vid);
|
|
if (idx >= 0)
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_VLAN);
|
|
cpsw_ale_set_vlan_id(ale_entry, vid);
|
|
|
|
cpsw_ale_set_vlan_untag_force(ale_entry, untag);
|
|
cpsw_ale_set_vlan_reg_mcast(ale_entry, reg_mcast);
|
|
cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast);
|
|
cpsw_ale_set_vlan_member_list(ale_entry, port);
|
|
|
|
if (idx < 0)
|
|
idx = cpsw_ale_match_free(ale);
|
|
if (idx < 0)
|
|
idx = cpsw_ale_find_ageable(ale);
|
|
if (idx < 0)
|
|
return -ENOMEM;
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_add_vlan);
|
|
|
|
int cpsw_ale_del_vlan(struct cpsw_ale *ale, u16 vid, int port_mask)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS] = {0, 0, 0};
|
|
int idx;
|
|
|
|
idx = cpsw_ale_match_vlan(ale, vid);
|
|
if (idx < 0)
|
|
return -ENOENT;
|
|
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
|
|
if (port_mask)
|
|
cpsw_ale_set_vlan_member_list(ale_entry, port_mask);
|
|
else
|
|
cpsw_ale_set_entry_type(ale_entry, ALE_TYPE_FREE);
|
|
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_del_vlan);
|
|
|
|
void cpsw_ale_set_allmulti(struct cpsw_ale *ale, int allmulti)
|
|
{
|
|
u32 ale_entry[ALE_ENTRY_WORDS];
|
|
int type, idx;
|
|
int unreg_mcast = 0;
|
|
|
|
/* Only bother doing the work if the setting is actually changing */
|
|
if (ale->allmulti == allmulti)
|
|
return;
|
|
|
|
/* Remember the new setting to check against next time */
|
|
ale->allmulti = allmulti;
|
|
|
|
for (idx = 0; idx < ale->params.ale_entries; idx++) {
|
|
cpsw_ale_read(ale, idx, ale_entry);
|
|
type = cpsw_ale_get_entry_type(ale_entry);
|
|
if (type != ALE_TYPE_VLAN)
|
|
continue;
|
|
|
|
unreg_mcast = cpsw_ale_get_vlan_unreg_mcast(ale_entry);
|
|
if (allmulti)
|
|
unreg_mcast |= 1;
|
|
else
|
|
unreg_mcast &= ~1;
|
|
cpsw_ale_set_vlan_unreg_mcast(ale_entry, unreg_mcast);
|
|
cpsw_ale_write(ale, idx, ale_entry);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_set_allmulti);
|
|
|
|
struct ale_control_info {
|
|
const char *name;
|
|
int offset, port_offset;
|
|
int shift, port_shift;
|
|
int bits;
|
|
};
|
|
|
|
static const struct ale_control_info ale_controls[ALE_NUM_CONTROLS] = {
|
|
[ALE_ENABLE] = {
|
|
.name = "enable",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 31,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_CLEAR] = {
|
|
.name = "clear",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 30,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_AGEOUT] = {
|
|
.name = "ageout",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 29,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_P0_UNI_FLOOD] = {
|
|
.name = "port0_unicast_flood",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 8,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_VLAN_NOLEARN] = {
|
|
.name = "vlan_nolearn",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 7,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_NO_PORT_VLAN] = {
|
|
.name = "no_port_vlan",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 6,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_OUI_DENY] = {
|
|
.name = "oui_deny",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 5,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_BYPASS] = {
|
|
.name = "bypass",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 4,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_RATE_LIMIT_TX] = {
|
|
.name = "rate_limit_tx",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 3,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_VLAN_AWARE] = {
|
|
.name = "vlan_aware",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 2,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_AUTH_ENABLE] = {
|
|
.name = "auth_enable",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 1,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_RATE_LIMIT] = {
|
|
.name = "rate_limit",
|
|
.offset = ALE_CONTROL,
|
|
.port_offset = 0,
|
|
.shift = 0,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_PORT_STATE] = {
|
|
.name = "port_state",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 0,
|
|
.port_shift = 0,
|
|
.bits = 2,
|
|
},
|
|
[ALE_PORT_DROP_UNTAGGED] = {
|
|
.name = "drop_untagged",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 2,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_PORT_DROP_UNKNOWN_VLAN] = {
|
|
.name = "drop_unknown",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 3,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_PORT_NOLEARN] = {
|
|
.name = "nolearn",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 4,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_PORT_NO_SA_UPDATE] = {
|
|
.name = "no_source_update",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 5,
|
|
.port_shift = 0,
|
|
.bits = 1,
|
|
},
|
|
[ALE_PORT_MCAST_LIMIT] = {
|
|
.name = "mcast_limit",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 16,
|
|
.port_shift = 0,
|
|
.bits = 8,
|
|
},
|
|
[ALE_PORT_BCAST_LIMIT] = {
|
|
.name = "bcast_limit",
|
|
.offset = ALE_PORTCTL,
|
|
.port_offset = 4,
|
|
.shift = 24,
|
|
.port_shift = 0,
|
|
.bits = 8,
|
|
},
|
|
[ALE_PORT_UNKNOWN_VLAN_MEMBER] = {
|
|
.name = "unknown_vlan_member",
|
|
.offset = ALE_UNKNOWNVLAN,
|
|
.port_offset = 0,
|
|
.shift = 0,
|
|
.port_shift = 0,
|
|
.bits = 6,
|
|
},
|
|
[ALE_PORT_UNKNOWN_MCAST_FLOOD] = {
|
|
.name = "unknown_mcast_flood",
|
|
.offset = ALE_UNKNOWNVLAN,
|
|
.port_offset = 0,
|
|
.shift = 8,
|
|
.port_shift = 0,
|
|
.bits = 6,
|
|
},
|
|
[ALE_PORT_UNKNOWN_REG_MCAST_FLOOD] = {
|
|
.name = "unknown_reg_flood",
|
|
.offset = ALE_UNKNOWNVLAN,
|
|
.port_offset = 0,
|
|
.shift = 16,
|
|
.port_shift = 0,
|
|
.bits = 6,
|
|
},
|
|
[ALE_PORT_UNTAGGED_EGRESS] = {
|
|
.name = "untagged_egress",
|
|
.offset = ALE_UNKNOWNVLAN,
|
|
.port_offset = 0,
|
|
.shift = 24,
|
|
.port_shift = 0,
|
|
.bits = 6,
|
|
},
|
|
};
|
|
|
|
int cpsw_ale_control_set(struct cpsw_ale *ale, int port, int control,
|
|
int value)
|
|
{
|
|
const struct ale_control_info *info;
|
|
int offset, shift;
|
|
u32 tmp, mask;
|
|
|
|
if (control < 0 || control >= ARRAY_SIZE(ale_controls))
|
|
return -EINVAL;
|
|
|
|
info = &ale_controls[control];
|
|
if (info->port_offset == 0 && info->port_shift == 0)
|
|
port = 0; /* global, port is a dont care */
|
|
|
|
if (port < 0 || port > ale->params.ale_ports)
|
|
return -EINVAL;
|
|
|
|
mask = BITMASK(info->bits);
|
|
if (value & ~mask)
|
|
return -EINVAL;
|
|
|
|
offset = info->offset + (port * info->port_offset);
|
|
shift = info->shift + (port * info->port_shift);
|
|
|
|
tmp = __raw_readl(ale->params.ale_regs + offset);
|
|
tmp = (tmp & ~(mask << shift)) | (value << shift);
|
|
__raw_writel(tmp, ale->params.ale_regs + offset);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_control_set);
|
|
|
|
int cpsw_ale_control_get(struct cpsw_ale *ale, int port, int control)
|
|
{
|
|
const struct ale_control_info *info;
|
|
int offset, shift;
|
|
u32 tmp;
|
|
|
|
if (control < 0 || control >= ARRAY_SIZE(ale_controls))
|
|
return -EINVAL;
|
|
|
|
info = &ale_controls[control];
|
|
if (info->port_offset == 0 && info->port_shift == 0)
|
|
port = 0; /* global, port is a dont care */
|
|
|
|
if (port < 0 || port > ale->params.ale_ports)
|
|
return -EINVAL;
|
|
|
|
offset = info->offset + (port * info->port_offset);
|
|
shift = info->shift + (port * info->port_shift);
|
|
|
|
tmp = __raw_readl(ale->params.ale_regs + offset) >> shift;
|
|
return tmp & BITMASK(info->bits);
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_control_get);
|
|
|
|
static void cpsw_ale_timer(unsigned long arg)
|
|
{
|
|
struct cpsw_ale *ale = (struct cpsw_ale *)arg;
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_AGEOUT, 1);
|
|
|
|
if (ale->ageout) {
|
|
ale->timer.expires = jiffies + ale->ageout;
|
|
add_timer(&ale->timer);
|
|
}
|
|
}
|
|
|
|
int cpsw_ale_set_ageout(struct cpsw_ale *ale, int ageout)
|
|
{
|
|
del_timer_sync(&ale->timer);
|
|
ale->ageout = ageout * HZ;
|
|
if (ale->ageout) {
|
|
ale->timer.expires = jiffies + ale->ageout;
|
|
add_timer(&ale->timer);
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_set_ageout);
|
|
|
|
void cpsw_ale_start(struct cpsw_ale *ale)
|
|
{
|
|
u32 rev;
|
|
|
|
rev = __raw_readl(ale->params.ale_regs + ALE_IDVER);
|
|
dev_dbg(ale->params.dev, "initialized cpsw ale revision %d.%d\n",
|
|
ALE_VERSION_MAJOR(rev), ALE_VERSION_MINOR(rev));
|
|
cpsw_ale_control_set(ale, 0, ALE_ENABLE, 1);
|
|
cpsw_ale_control_set(ale, 0, ALE_CLEAR, 1);
|
|
|
|
init_timer(&ale->timer);
|
|
ale->timer.data = (unsigned long)ale;
|
|
ale->timer.function = cpsw_ale_timer;
|
|
if (ale->ageout) {
|
|
ale->timer.expires = jiffies + ale->ageout;
|
|
add_timer(&ale->timer);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_start);
|
|
|
|
void cpsw_ale_stop(struct cpsw_ale *ale)
|
|
{
|
|
del_timer_sync(&ale->timer);
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_stop);
|
|
|
|
struct cpsw_ale *cpsw_ale_create(struct cpsw_ale_params *params)
|
|
{
|
|
struct cpsw_ale *ale;
|
|
|
|
ale = kzalloc(sizeof(*ale), GFP_KERNEL);
|
|
if (!ale)
|
|
return NULL;
|
|
|
|
ale->params = *params;
|
|
ale->ageout = ale->params.ale_ageout * HZ;
|
|
|
|
return ale;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_create);
|
|
|
|
int cpsw_ale_destroy(struct cpsw_ale *ale)
|
|
{
|
|
if (!ale)
|
|
return -EINVAL;
|
|
cpsw_ale_control_set(ale, 0, ALE_ENABLE, 0);
|
|
kfree(ale);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_destroy);
|
|
|
|
void cpsw_ale_dump(struct cpsw_ale *ale, u32 *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ale->params.ale_entries; i++) {
|
|
cpsw_ale_read(ale, i, data);
|
|
data += ALE_ENTRY_WORDS;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(cpsw_ale_dump);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("TI CPSW ALE driver");
|
|
MODULE_AUTHOR("Texas Instruments");
|