1483 lines · 36.0 KB · C
RawDownload
/* 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 * * 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. * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #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/timer.h>#include <linux/workqueue.h>#include <linux/mdio.h>#include <linux/io.h>#include <linux/uaccess.h>#include <linux/atomic.h> #include <asm/irq.h> static const char *phy_speed_to_str(int speed){	switch (speed) {	case SPEED_10:		return "10Mbps";	case SPEED_100:		return "100Mbps";	case SPEED_1000:		return "1Gbps";	case SPEED_2500:		return "2.5Gbps";	case SPEED_5000:		return "5Gbps";	case SPEED_10000:		return "10Gbps";	case SPEED_14000:		return "14Gbps";	case SPEED_20000:		return "20Gbps";	case SPEED_25000:		return "25Gbps";	case SPEED_40000:		return "40Gbps";	case SPEED_50000:		return "50Gbps";	case SPEED_56000:		return "56Gbps";	case SPEED_100000:		return "100Gbps";	case SPEED_UNKNOWN:		return "Unknown";	default:		return "Unsupported (update phy.c)";	}} #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(STARTING)	PHY_STATE_STR(READY)	PHY_STATE_STR(PENDING)	PHY_STATE_STR(UP)	PHY_STATE_STR(AN)	PHY_STATE_STR(RUNNING)	PHY_STATE_STR(NOLINK)	PHY_STATE_STR(FORCING)	PHY_STATE_STR(CHANGELINK)	PHY_STATE_STR(HALTED)	PHY_STATE_STR(RESUMING)	} 	return NULL;}  /** * 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),			DUPLEX_FULL == phydev->duplex ? "Full" : "Half",			phydev->pause ? "rx/tx" : "off");	} 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 phy_device *phydev, u32 interrupts){	phydev->interrupts = interrupts;	if (phydev->drv->config_intr)		return phydev->drv->config_intr(phydev); 	return 0;} /** * phy_restart_aneg - restart auto-negotiation * @phydev: target phy_device struct * * Restart the autonegotiation on @phydev.  Returns >= 0 on success or * negative errno on error. */int phy_restart_aneg(struct phy_device *phydev){	int ret; 	if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))		ret = genphy_c45_restart_aneg(phydev);	else		ret = genphy_restart_aneg(phydev); 	return ret;}EXPORT_SYMBOL_GPL(phy_restart_aneg); /** * phy_aneg_done - return auto-negotiation status * @phydev: target phy_device struct * * Description: Return the auto-negotiation status from this @phydev * Returns > 0 on success or < 0 on error. 0 means that auto-negotiation * is still pending. */int phy_aneg_done(struct phy_device *phydev){	if (phydev->drv && phydev->drv->aneg_done)		return phydev->drv->aneg_done(phydev); 	/* Avoid genphy_aneg_done() if the Clause 45 PHY does not	 * implement Clause 22 registers	 */	if (phydev->is_c45 && !(phydev->c45_ids.devices_in_package & BIT(0)))		return -EINVAL; 	return genphy_aneg_done(phydev);}EXPORT_SYMBOL(phy_aneg_done); /* A structure for mapping a particular speed and duplex * combination to a particular SUPPORTED and ADVERTISED value */struct phy_setting {	int speed;	int duplex;	u32 setting;}; /* A mapping of all SUPPORTED settings to speed/duplex.  This table * must be grouped by speed and sorted in descending match priority * - iow, descending speed. */static const struct phy_setting settings[] = {	{		.speed = SPEED_10000,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_10000baseKR_Full,	},	{		.speed = SPEED_10000,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_10000baseKX4_Full,	},	{		.speed = SPEED_10000,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_10000baseT_Full,	},	{		.speed = SPEED_2500,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_2500baseX_Full,	},	{		.speed = SPEED_1000,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_1000baseKX_Full,	},	{		.speed = SPEED_1000,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_1000baseT_Full,	},	{		.speed = SPEED_1000,		.duplex = DUPLEX_HALF,		.setting = SUPPORTED_1000baseT_Half,	},	{		.speed = SPEED_100,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_100baseT_Full,	},	{		.speed = SPEED_100,		.duplex = DUPLEX_HALF,		.setting = SUPPORTED_100baseT_Half,	},	{		.speed = SPEED_10,		.duplex = DUPLEX_FULL,		.setting = SUPPORTED_10baseT_Full,	},	{		.speed = SPEED_10,		.duplex = DUPLEX_HALF,		.setting = SUPPORTED_10baseT_Half,	},}; /** * phy_lookup_setting - lookup a PHY setting * @speed: speed to match * @duplex: duplex to match * @features: allowed link modes * @exact: an exact match is required * * Search the settings array for a setting that matches the speed and * duplex, and which is supported. * * If @exact is unset, either an exact match or %NULL for no match will * be returned. * * If @exact is set, an exact match, the fastest supported setting at * or below the specified speed, the slowest supported setting, or if * they all fail, %NULL will be returned. */static const struct phy_setting *phy_lookup_setting(int speed, int duplex, u32 features, bool exact){	const struct phy_setting *p, *match = NULL, *last = NULL;	int i; 	for (i = 0, p = settings; i < ARRAY_SIZE(settings); i++, p++) {		if (p->setting & features) {			last = p;			if (p->speed == speed && p->duplex == duplex) {				/* Exact match for speed and duplex */				match = p;				break;			} else if (!exact) {				if (!match && p->speed <= speed)					/* Candidate */					match = p; 				if (p->speed < speed)					break;			}		}	} 	if (!match && !exact)		match = last; 	return match;} /** * phy_find_valid - find a PHY setting that matches the requested parameters * @speed: desired speed * @duplex: desired duplex * @supported: mask of supported link modes * * Locate a supported phy setting that is, in priority order: * - an exact match for the specified speed and duplex mode * - a match for the specified speed, or slower speed * - the slowest supported speed * Returns the matched phy_setting entry, or %NULL if no supported phy * settings were found. */static const struct phy_setting *phy_find_valid(int speed, int duplex, u32 supported){	return phy_lookup_setting(speed, duplex, supported, false);} /** * phy_supported_speeds - return all speeds currently supported by a phy device * @phy: The phy device to return supported speeds of. * @speeds: buffer to store supported speeds in. * @size:   size of speeds buffer. * * Description: Returns the number of supported speeds, and fills the speeds * buffer with the supported speeds. If speeds buffer is too small to contain * all currently supported speeds, will return as many speeds as can fit. */unsigned int phy_supported_speeds(struct phy_device *phy,				  unsigned int *speeds,				  unsigned int size){	unsigned int count = 0;	unsigned int idx = 0; 	for (idx = 0; idx < ARRAY_SIZE(settings) && count < size; idx++)		/* Assumes settings are grouped by speed */		if ((settings[idx].setting & phy->supported) &&		    (count == 0 || speeds[count - 1] != settings[idx].speed))			speeds[count++] = settings[idx].speed; 	return count;} /** * phy_check_valid - check if there is a valid PHY setting which matches *		     speed, duplex, and feature mask * @speed: speed to match * @duplex: duplex to match * @features: A mask of the valid settings * * Description: Returns true if there is a valid setting, false otherwise. */static inline bool phy_check_valid(int speed, int duplex, u32 features){	return !!phy_lookup_setting(speed, duplex, features, true);} /** * phy_sanitize_settings - make sure the PHY is set to supported speed and duplex * @phydev: the target phy_device struct * * Description: Make sure the PHY is set to supported speeds and *   duplexes.  Drop down by one in this order:  1000/FULL, *   1000/HALF, 100/FULL, 100/HALF, 10/FULL, 10/HALF. */static void phy_sanitize_settings(struct phy_device *phydev){	const struct phy_setting *setting;	u32 features = phydev->supported; 	/* Sanitize settings based on PHY capabilities */	if ((features & SUPPORTED_Autoneg) == 0)		phydev->autoneg = AUTONEG_DISABLE; 	setting = phy_find_valid(phydev->speed, phydev->duplex, features);	if (setting) {		phydev->speed = setting->speed;		phydev->duplex = setting->duplex;	} else {		/* We failed to find anything (no supported speeds?) */		phydev->speed = SPEED_UNKNOWN;		phydev->duplex = DUPLEX_UNKNOWN;	}} /** * phy_ethtool_sset - generic ethtool sset function, handles all the details * @phydev: target phy_device struct * @cmd: ethtool_cmd * * A few notes about parameter checking: * * - We don't set port or transceiver, so we don't care what they *   were set to. * - phy_start_aneg() will make sure forced settings are sane, and *   choose the next best ones from the ones selected, so we don't *   care if ethtool tries to give us bad values. */int phy_ethtool_sset(struct phy_device *phydev, struct ethtool_cmd *cmd){	u32 speed = ethtool_cmd_speed(cmd); 	if (cmd->phy_address != phydev->mdio.addr)		return -EINVAL; 	/* We make sure that we don't pass unsupported values in to the PHY */	cmd->advertising &= phydev->supported; 	/* Verify the settings we care about. */	if (cmd->autoneg != AUTONEG_ENABLE && cmd->autoneg != AUTONEG_DISABLE)		return -EINVAL; 	if (cmd->autoneg == AUTONEG_ENABLE && cmd->advertising == 0)		return -EINVAL; 	if (cmd->autoneg == AUTONEG_DISABLE &&	    ((speed != SPEED_1000 &&	      speed != SPEED_100 &&	      speed != SPEED_10) ||	     (cmd->duplex != DUPLEX_HALF &&	      cmd->duplex != DUPLEX_FULL)))		return -EINVAL; 	phydev->autoneg = cmd->autoneg; 	phydev->speed = speed; 	phydev->advertising = cmd->advertising; 	if (AUTONEG_ENABLE == cmd->autoneg)		phydev->advertising |= ADVERTISED_Autoneg;	else		phydev->advertising &= ~ADVERTISED_Autoneg; 	phydev->duplex = cmd->duplex; 	phydev->mdix_ctrl = cmd->eth_tp_mdix_ctrl; 	/* Restart the PHY */	phy_start_aneg(phydev); 	return 0;}EXPORT_SYMBOL(phy_ethtool_sset); int phy_ethtool_ksettings_set(struct phy_device *phydev,			      const struct ethtool_link_ksettings *cmd){	u8 autoneg = cmd->base.autoneg;	u8 duplex = cmd->base.duplex;	u32 speed = cmd->base.speed;	u32 advertising; 	if (cmd->base.phy_address != phydev->mdio.addr)		return -EINVAL; 	ethtool_convert_link_mode_to_legacy_u32(&advertising,						cmd->link_modes.advertising); 	/* We make sure that we don't pass unsupported values in to the PHY */	advertising &= phydev->supported; 	/* Verify the settings we care about. */	if (autoneg != AUTONEG_ENABLE && autoneg != AUTONEG_DISABLE)		return -EINVAL; 	if (autoneg == AUTONEG_ENABLE && advertising == 0)		return -EINVAL; 	if (autoneg == AUTONEG_DISABLE &&	    ((speed != SPEED_1000 &&	      speed != SPEED_100 &&	      speed != SPEED_10) ||	     (duplex != DUPLEX_HALF &&	      duplex != DUPLEX_FULL)))		return -EINVAL; 	phydev->autoneg = autoneg; 	phydev->speed = speed; 	phydev->advertising = advertising; 	if (autoneg == AUTONEG_ENABLE)		phydev->advertising |= ADVERTISED_Autoneg;

Lines 1–500 of 1483.