// SPDX-License-Identifier: GPL-2.0+
/* Framework for configuring and reading PHY devices
* Based on code in sungem_phy.c and gianfar_phy.c
*
* Author: Andy Fleming
*
* Copyright (c) 2004 Freescale Semiconductor, Inc.
* Copyright (c) 2006, 2007 Maciej W. Rozycki
*/
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/unistd.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/mii.h>
#include <linux/ethtool.h>
#include <linux/phy.h>
#include <linux/phy_led_triggers.h>
#include <linux/workqueue.h>
#include <linux/mdio.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#define PHY_STATE_TIME HZ
#define PHY_STATE_STR(_state) \
case PHY_##_state: \
return __stringify(_state); \
static const char *phy_state_to_str(enum phy_state st)
{
switch (st) {
PHY_STATE_STR(DOWN)
PHY_STATE_STR(READY)
PHY_STATE_STR(UP)
PHY_STATE_STR(RUNNING)
PHY_STATE_STR(NOLINK)
PHY_STATE_STR(HALTED)
}
return NULL;
}
static void phy_link_up(struct phy_device *phydev)
{
phydev->phy_link_change(phydev, true, true);
phy_led_trigger_change_speed(phydev);
}
static void phy_link_down(struct phy_device *phydev, bool do_carrier)
{
phydev->phy_link_change(phydev, false, do_carrier);
phy_led_trigger_change_speed(phydev);
}
static const char *phy_pause_str(struct phy_device *phydev)
{
bool local_pause, local_asym_pause;
if (phydev->autoneg == AUTONEG_DISABLE)
goto no_pause;
local_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Pause_BIT,
phydev->advertising);
local_asym_pause = linkmode_test_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT,
phydev->advertising);
if (local_pause && phydev->pause)
return "rx/tx";
if (local_asym_pause && phydev->asym_pause) {
if (local_pause)
return "rx";
if (phydev->pause)
return "tx";
}
no_pause:
return "off";
}
/**
* phy_print_status - Convenience function to print out the current phy status
* @phydev: the phy_device struct
*/
void phy_print_status(struct phy_device *phydev)
{
if (phydev->link) {
netdev_info(phydev->attached_dev,
"Link is Up - %s/%s - flow control %s\n",
phy_speed_to_str(phydev->speed),
phy_duplex_to_str(phydev->duplex),
phy_pause_str(phydev));
} else {
netdev_info(phydev->attached_dev, "Link is Down\n");
}
}
EXPORT_SYMBOL(phy_print_status);
/**
* phy_clear_interrupt - Ack the phy device's interrupt
* @phydev: the phy_device struct
*
* If the @phydev driver has an ack_interrupt function, call it to
* ack and clear the phy device's interrupt.
*
* Returns 0 on success or < 0 on error.
*/
static int phy_clear_interrupt(struct phy_device *phydev)
{
if (phydev->drv->ack_interrupt)
return phydev->drv->ack_interrupt(phydev);
return 0;
}
/**
* phy_config_interrupt - configure the PHY device for the requested interrupts
* @phydev: the phy_device struct
* @interrupts: interrupt flags to configure for this @phydev
*
* Returns 0 on success or < 0 on error.
*/
static int phy_config_interrupt(struct