// SPDX-License-Identifier: GPL-2.0
/*
* Microchip KSZ9477 switch driver main logic
*
* Copyright (C) 2017-2019 Microchip Technology Inc.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/iopoll.h>
#include <linux/platform_data/microchip-ksz.h>
#include <linux/phy.h>
#include <linux/if_bridge.h>
#include <linux/if_vlan.h>
#include <net/dsa.h>
#include <net/switchdev.h>
#include "ksz9477_reg.h"
#include "ksz_common.h"
#include "ksz9477.h"
static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
{
regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0);
}
static void ksz_port_cfg(struct ksz_device *dev, int port, int offset, u8 bits,
bool set)
{
regmap_update_bits(ksz_regmap_8(dev), PORT_CTRL_ADDR(port, offset),
bits, set ? bits : 0);
}
static void ksz9477_cfg32(struct ksz_device *dev, u32 addr, u32 bits, bool set)
{
regmap_update_bits(ksz_regmap_32(dev), addr, bits, set ? bits : 0);
}
static void ksz9477_port_cfg32(struct ksz_device *dev, int port, int offset,
u32 bits, bool set)
{
regmap_update_bits(ksz_regmap_32(dev), PORT_CTRL_ADDR(port, offset),
bits, set ? bits : 0);
}
int ksz9477_change_mtu(struct ksz_device *dev, int port, int mtu)
{
u16 frame_size;
if (!dsa_is_cpu_port(dev->ds, port))
return 0;
frame_size = mtu + VLAN_ETH_HLEN + ETH_FCS_LEN;
return regmap_update_bits(ksz_regmap_16(dev), REG_SW_MTU__2,
REG_SW_MTU_MASK, frame_size);
}
/**
* ksz9477_handle_wake_reason - Handle wake reason on a specified port.
* @dev: The device structure.
* @port: The port number.
*
* This function reads the PME (Power Management Event) status register of a
* specified port to determine the wake reason. If there is no wake event, it
* returns early. Otherwise, it logs the wake reason which could be due to a
* "Magic Packet", "Link Up", or "Energy Detect" event. The PME status register
* is then cleared to acknowledge the handling of the wake event.
*
* Return: 0 on success, or an error code on failure.
*/
static int ksz9477_handle_wake_reason(struct ksz_device *dev, int port)
{
u8 pme_status;
int ret;
ret = ksz_pread8(dev, port, REG_PORT_PME_STATUS, &pme_status);
if (ret)
return ret;
if (!pme_status)
return 0;
dev_dbg(dev->dev, "Wake event on port %d due to:%s%s%s\n", port,
pme_status & PME_WOL_MAGICPKT ? " \"Magic Packet\"" : "",
pme_status & PME_WOL_LINKUP ? " \"Link Up\"" : "",
pme_status & PME_WOL_ENERGY ? " \"Energy detect\"" : "");
return ksz_pwrite8(dev, port, REG_PORT_PME_STATUS, pme_status);
}
/**
* ksz9477_get_wol - Get Wake-on-LAN settings for a specified port.
* @dev: The device structure.
* @port: The port number.
* @wol: Pointer to ethtool Wake-on-LAN settings structure.
*
* This function checks the PME Pin Control Register to see if PME Pin Output
* Enable is set, indicating PME is enabled. If enabled, it sets the supported
* and active WoL flags.
*/
void ksz9477_get_wol(struct ksz_device *dev, int port,
struct ethtool_wolinfo *wol)
{
u8 pme_ctrl;
int ret;
if (!dev->wakeup_source)
return;
wol->supported = WAKE_PHY;
/* Check if the current MAC address on this port can be set
* as global for WAKE_MAGIC support. The result may vary
* dynamically based on other ports configurations.
*/
if (ksz_is_port_mac_global_usable(dev->ds, port))
wol->supported |= WAKE_MAGIC;
ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl);
if (ret)
return;
if (pme_ctrl & PME_WOL_MAGICPKT)
wol->wolopts |= WAKE_MAGIC;
if (pme_ctrl & (PME_WOL_LINKUP | PME_WOL_ENERGY))
wol->wolopts |= WAKE_PHY;
}
/**
* ksz9477_set_wol - Set Wake-on-LAN settings for a specified port.
* @dev: The device structure.
* @port: The port number.
* @wol: Pointer to ethtool Wake-on-LAN settings structure.
*
* This function configures Wake-on-LAN (WoL) settings for a specified port.
* It validates the provided WoL options, checks if PME is enabled via the
* switch's PME Pin Control Register, clears any previous wake reasons,
* and sets the Magic Packet flag in the port's PME control register if
* specified.
*
* Return: 0 on success, or other error codes on failure.
*/
int ksz9477_set_wol(struct ksz_device *dev, int port,
struct ethtool_wolinfo *wol)
{
u8 pme_ctrl = 0, pme_ctrl_old = 0;
bool magic_switched_off;
bool magic_switched_on;
int ret;
if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC))
return -EINVAL;
if (!dev->wakeup_source)
return -EOPNOTSUPP;
ret = ksz9477_handle_wake_reason(dev, port);
if (ret)
return ret;
if (wol->wolopts & WAKE_MAGIC)
pme_ctrl |= PME_WOL_MAGICPKT;
if (wol->wolopts & WAKE_PHY)
pme_ctrl |= PME_WOL_LINKUP | PME_WOL_ENERGY;
ret = ksz_pread8(dev, port, REG_PORT_PME_CTRL, &pme_ctrl_old);
if (ret)
return ret;
if (pme_ctrl_old == pme_ctrl)
return 0;
magic_switched_off = (pme_ctrl_old & PME_WOL_MAGICPKT) &&
!(pme_ctrl & PME_WOL_MAGICPKT);
magic_switched_on = !(pme_ctrl_old & PME_WOL_MAGICPKT) &&
(pme_ctrl & PME_WOL_MAGICPKT);
/* To keep reference count of MAC address, we should do this
* operation only on change of WOL settings.
*/
if (magic_switched_on) {
ret = ksz_switch_macaddr_get(dev->ds, port, NULL);
if (ret)
return ret;
} else if (magic_switched_off) {
ksz_switch_macaddr_put(dev->ds);
}
ret = ksz_pwrite8(dev, port, REG_PORT_PME_CTRL, pme_ctrl);
if (ret) {
if (magic_switched_on)
ksz_switch_macaddr_put(dev->ds);
return ret;
}
return 0;
}
/**
* ksz9477_wol_pre_shutdown - Prepares the switch device for shutdown while
* considering Wake-on-LAN (WoL) settings.
* @dev: The switch device structure.
* @wol_enabled: Pointer to a boolean which will be set to true if WoL is
* enabled on any port.
*
* This function prepares the switch device for a safe shutdown while taking
* into account the Wake-on-LAN (WoL) settings on the user ports. It updates
* the wol_enabled flag accordingly to reflect whether WoL is active on any
* port.
*/
void ksz9477_wol_pre_shutdown(struct ksz_device *dev, bool *wol_enabled)
{
struct dsa_port *dp;
int ret;
*wol_enabled = false;
if (!dev->wakeup_source)
return;
dsa_switch_for_each_user_port(dp, dev->ds) {
u8 pme_ctrl = 0;
ret = ksz_pread8(dev, dp->index, REG_PORT_PME_CTRL, &pme_ctrl);
if (!ret && pme_ctrl)
*wol_enabled = true;
/* make sure there are no pending wake events which would
* prevent the device from going to sleep/shutdown.
*/
ksz9477_handle_wake_reason(dev, dp->index);
}
/* Now we are save to enable PME pin. */
if (*wol_enabled)
ksz_write8(dev, REG_SW_PME_CTRL, PME_ENABLE);
}
static int ksz9477_wait_vlan_ctrl_ready(struct ksz_device *dev)
{
unsigned int val;
return regmap_read_poll_timeout(ksz_regmap_8(dev), REG_SW_VLAN_CTRL,
val, !(val & VLAN_START), 10, 1000);
}
static int ksz9477_get_vlan_table(struct ksz_device *dev, u16 vid,
u32 *vlan_table)
{
int ret;
mutex_lock(&dev->vlan_mutex);
ksz_write16(dev, REG_SW_VLAN_ENTRY_INDEX__2, vid & VLAN_INDEX_M);
ksz_write8(dev, REG_SW_VLAN_CTRL, VLAN_READ | VLAN_START);
/* wait to be cleared */
ret = ksz9477_wait_vlan_ctrl_ready(dev);
if (ret) {
dev_dbg(dev->dev, "Failed to read vlan table\n");
goto exit;
}
ksz_read32(dev, REG_SW_VLAN_ENTRY__4, &vlan_table[0]);
ksz_read32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, &vlan_table[1]);
ksz_read32(dev, REG_SW_VLAN_ENTRY_PORTS__4, &vlan_table[2]);
ksz_write8(dev, REG_SW_VLAN_CTRL, 0);
exit:
mutex_unlock(&dev->vlan_mutex);
return ret;
}
static int ksz9477_set_vlan_table(struct ksz_device *dev, u16 vid,
u32 *vlan_table)
{
int ret;
mutex_lock(&dev->vlan_mutex);
ksz_write32(dev, REG_SW_VLAN_ENTRY__4, vlan_table[0]);
ksz_write32(dev, REG_SW_VLAN_ENTRY_UNTAG__4, vlan_table[1]);
ksz_write32(dev, REG
|