blob: 8a5111f9414c07ae3aebc3950a7e64b337f827ac [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Microchip switch driver main logic
*
* Copyright (C) 2017-2018 Microchip Technology Inc.
*/
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_data/microchip-ksz.h>
#include <linux/phy.h>
#include <linux/etherdevice.h>
#include <linux/if_bridge.h>
#include <linux/of_net.h>
#include <net/dsa.h>
#include <net/switchdev.h>
#include "ksz_priv.h"
void ksz_update_port_member(struct ksz_device *dev, int port)
{
struct ksz_port *p;
int i;
for (i = 0; i < dev->port_cnt; i++) {
if (i == port || i == dev->cpu_port)
continue;
p = &dev->ports[i];
if (!(dev->member & (1 << i)))
continue;
/* Port is a member of the bridge and is forwarding. */
if (p->stp_state == BR_STATE_FORWARDING &&
p->member != dev->member)
dev->dev_ops->cfg_port_member(dev, i, dev->member);
}
}
EXPORT_SYMBOL_GPL(ksz_update_port_member);
int ksz_phy_read16(struct dsa_switch *ds, int addr, int reg)
{
struct ksz_device *dev = ds->priv;
u16 val = 0xffff;
dev->dev_ops->r_phy(dev, addr, reg, &val);
return val;
}
EXPORT_SYMBOL_GPL(ksz_phy_read16);
int ksz_phy_write16(struct dsa_switch *ds, int addr, int reg, u16 val)
{
struct ksz_device *dev = ds->priv;
dev->dev_ops->w_phy(dev, addr, reg, val);
return 0;
}
EXPORT_SYMBOL_GPL(ksz_phy_write16);
int ksz_sset_count(struct dsa_switch *ds, int port, int sset)
{
struct ksz_device *dev = ds->priv;
if (sset != ETH_SS_STATS)
return 0;
return dev->mib_cnt;
}
EXPORT_SYMBOL_GPL(ksz_sset_count);
int ksz_port_bridge_join(struct dsa_switch *ds, int port,
struct net_device *br)
{
struct ksz_device *dev = ds->priv;
dev->br_member |= (1 << port);
/* port_stp_state_set() will be called after to put the port in
* appropriate state so there is no need to do anything.
*/
return 0;
}
EXPORT_SYMBOL_GPL(ksz_port_bridge_join);
void ksz_port_bridge_leave(struct dsa_switch *ds, int port,
struct net_device *br)
{
struct ksz_device *dev = ds->priv;
dev->br_member &= ~(1 << port);
dev->member &= ~(1 << port);
/* port_stp_state_set() will be called after to put the port in
* forwarding state so there is no need to do anything.
*/
}
EXPORT_SYMBOL_GPL(ksz_port_bridge_leave);
void ksz_port_fast_age(struct dsa_switch *ds, int port)
{
struct ksz_device *dev = ds->priv;
dev->dev_ops->flush_dyn_mac_table(dev, port);
}
EXPORT_SYMBOL_GPL(ksz_port_fast_age);
int ksz_port_vlan_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_vlan *vlan)
{
/* nothing needed */
return 0;
}
EXPORT_SYMBOL_GPL(ksz_port_vlan_prepare);
int ksz_port_fdb_dump(struct dsa_switch *ds, int port, dsa_fdb_dump_cb_t *cb,
void *data)
{
struct ksz_device *dev = ds->priv;
int ret = 0;
u16 i = 0;
u16 entries = 0;
u8 timestamp = 0;
u8 fid;
u8 member;
struct alu_struct alu;
do {
alu.is_static = false;
ret = dev->dev_ops->r_dyn_mac_table(dev, i, alu.mac, &fid,
&member, &timestamp,
&entries);
if (!ret && (member & BIT(port))) {
ret = cb(alu.mac, alu.fid, alu.is_static, data);
if (ret)
break;
}
i++;
} while (i < entries);
if (i >= entries)
ret = 0;
return ret;
}
EXPORT_SYMBOL_GPL(ksz_port_fdb_dump);
int ksz_port_mdb_prepare(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb)
{
/* nothing to do */
return 0;
}
EXPORT_SYMBOL_GPL(ksz_port_mdb_prepare);
void ksz_port_mdb_add(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb)
{
struct ksz_device *dev = ds->priv;
struct alu_struct alu;
int index;
int empty = 0;
alu.port_forward = 0;
for (index = 0; index < dev->num_statics; index++) {
if (!dev->dev_ops->r_sta_mac_table(dev, index, &alu)) {
/* Found one already in static MAC table. */
if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
alu.fid == mdb->vid)
break;
/* Remember the first empty entry. */
} else if (!empty) {
empty = index + 1;
}
}
/* no available entry */
if (index == dev->num_statics && !empty)
return;
/* add entry */
if (index == dev->num_statics) {
index = empty - 1;
memset(&alu, 0, sizeof(alu));
memcpy(alu.mac, mdb->addr, ETH_ALEN);
alu.is_static = true;
}
alu.port_forward |= BIT(port);
if (mdb->vid) {
alu.is_use_fid = true;
/* Need a way to map VID to FID. */
alu.fid = mdb->vid;
}
dev->dev_ops->w_sta_mac_table(dev, index, &alu);
}
EXPORT_SYMBOL_GPL(ksz_port_mdb_add);
int ksz_port_mdb_del(struct dsa_switch *ds, int port,
const struct switchdev_obj_port_mdb *mdb)
{
struct ksz_device *dev = ds->priv;
struct alu_struct alu;
int index;
int ret = 0;
for (index = 0; index < dev->num_statics; index++) {
if (!dev->dev_ops->r_sta_mac_table(dev, index, &alu)) {
/* Found one already in static MAC table. */
if (!memcmp(alu.mac, mdb->addr, ETH_ALEN) &&
alu.fid == mdb->vid)
break;
}
}
/* no available entry */
if (index == dev->num_statics)
goto exit;
/* clear port */
alu.port_forward &= ~BIT(port);
if (!alu.port_forward)
alu.is_static = false;
dev->dev_ops->w_sta_mac_table(dev, index, &alu);
exit:
return ret;
}
EXPORT_SYMBOL_GPL(ksz_port_mdb_del);
int ksz_enable_port(struct dsa_switch *ds, int port, struct phy_device *phy)
{
struct ksz_device *dev = ds->priv;
/* setup slave port */
dev->dev_ops->port_setup(dev, port, false);
/* port_stp_state_set() will be called after to enable the port so
* there is no need to do anything.
*/
return 0;
}
EXPORT_SYMBOL_GPL(ksz_enable_port);
void ksz_disable_port(struct dsa_switch *ds, int port, struct phy_device *phy)
{
struct ksz_device *dev = ds->priv;
dev->on_ports &= ~(1 << port);
dev->live_ports &= ~(1 << port);
/* port_stp_state_set() will be called after to disable the port so
* there is no need to do anything.
*/
}
EXPORT_SYMBOL_GPL(ksz_disable_port);
struct ksz_device *ksz_switch_alloc(struct device *base,
const struct ksz_io_ops *ops,
void *priv)
{
struct dsa_switch *ds;
struct ksz_device *swdev;
ds = dsa_switch_alloc(base, DSA_MAX_PORTS);
if (!ds)
return NULL;
swdev = devm_kzalloc(base, sizeof(*swdev), GFP_KERNEL);
if (!swdev)
return NULL;
ds->priv = swdev;
swdev->dev = base;
swdev->ds = ds;
swdev->priv = priv;
swdev->ops = ops;
return swdev;
}
EXPORT_SYMBOL(ksz_switch_alloc);
int ksz_switch_register(struct ksz_device *dev,
const struct ksz_dev_ops *ops)
{
int ret;
if (dev->pdata)
dev->chip_id = dev->pdata->chip_id;
dev->reset_gpio = devm_gpiod_get_optional(dev->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(dev->reset_gpio))
return PTR_ERR(dev->reset_gpio);
if (dev->reset_gpio) {
gpiod_set_value(dev->reset_gpio, 1);
mdelay(10);
gpiod_set_value(dev->reset_gpio, 0);
}
mutex_init(&dev->reg_mutex);
mutex_init(&dev->stats_mutex);
mutex_init(&dev->alu_mutex);
mutex_init(&dev->vlan_mutex);
dev->dev_ops = ops;
if (dev->dev_ops->detect(dev))
return -EINVAL;
ret = dev->dev_ops->init(dev);
if (ret)
return ret;
dev->interface = PHY_INTERFACE_MODE_MII;
if (dev->dev->of_node) {
ret = of_get_phy_mode(dev->dev->of_node);
if (ret >= 0)
dev->interface = ret;
}
ret = dsa_register_switch(dev->ds);
if (ret) {
dev->dev_ops->exit(dev);
return ret;
}
return 0;
}
EXPORT_SYMBOL(ksz_switch_register);
void ksz_switch_remove(struct ksz_device *dev)
{
dev->dev_ops->exit(dev);
dsa_unregister_switch(dev->ds);
if (dev->reset_gpio)
gpiod_set_value(dev->reset_gpio, 1);
}
EXPORT_SYMBOL(ksz_switch_remove);
MODULE_AUTHOR("Woojung Huh <Woojung.Huh@microchip.com>");
MODULE_DESCRIPTION("Microchip KSZ Series Switch DSA Driver");
MODULE_LICENSE("GPL");