phy-core.c 8.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
/*
 * Core PHY library, taken from phy.c
 *
 * 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.
 */
#include <linux/export.h>
#include <linux/phy.h>

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
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-core.c)";
	}
}
EXPORT_SYMBOL_GPL(phy_speed_to_str);

const char *phy_duplex_to_str(unsigned int duplex)
{
	if (duplex == DUPLEX_HALF)
		return "Half";
	if (duplex == DUPLEX_FULL)
		return "Full";
	if (duplex == DUPLEX_UNKNOWN)
		return "Unknown";
	return "Unsupported (update phy-core.c)";
}
EXPORT_SYMBOL_GPL(phy_duplex_to_str);

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
/* 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,
		.bit = ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
	},
	{
		.speed = SPEED_10000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10000baseKX4_Full_BIT,
	},
	{
		.speed = SPEED_10000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
	},
	{
		.speed = SPEED_2500,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_2500baseX_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseKX_Full_BIT,
	},
90 91 92 93 94
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
	},
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
	},
	{
		.speed = SPEED_1000,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_1000baseT_Half_BIT,
	},
	{
		.speed = SPEED_100,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_100baseT_Full_BIT,
	},
	{
		.speed = SPEED_100,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_100baseT_Half_BIT,
	},
	{
		.speed = SPEED_10,
		.duplex = DUPLEX_FULL,
		.bit = ETHTOOL_LINK_MODE_10baseT_Full_BIT,
	},
	{
		.speed = SPEED_10,
		.duplex = DUPLEX_HALF,
		.bit = ETHTOOL_LINK_MODE_10baseT_Half_BIT,
	},
};

/**
 * phy_lookup_setting - lookup a PHY setting
 * @speed: speed to match
 * @duplex: duplex to match
 * @mask: allowed link modes
 * @maxbit: bit size of 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.
 */
const struct phy_setting *
phy_lookup_setting(int speed, int duplex, const unsigned long *mask,
		   size_t maxbit, 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->bit < maxbit && test_bit(p->bit, mask)) {
			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;
}
EXPORT_SYMBOL_GPL(phy_lookup_setting);

size_t phy_speeds(unsigned int *speeds, size_t size,
		  unsigned long *mask, size_t maxbit)
{
	size_t count;
	int i;

	for (i = 0, count = 0; i < ARRAY_SIZE(settings) && count < size; i++)
		if (settings[i].bit < maxbit &&
		    test_bit(settings[i].bit, mask) &&
		    (count == 0 || speeds[count - 1] != settings[i].speed))
			speeds[count++] = settings[i].speed;

	return count;
}

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
/**
 * phy_resolve_aneg_linkmode - resolve the advertisments into phy settings
 * @phydev: The phy_device struct
 *
 * Resolve our and the link partner advertisments into their corresponding
 * speed and duplex. If full duplex was negotiated, extract the pause mode
 * from the link partner mask.
 */
void phy_resolve_aneg_linkmode(struct phy_device *phydev)
{
	u32 common = phydev->lp_advertising & phydev->advertising;

	if (common & ADVERTISED_10000baseT_Full) {
		phydev->speed = SPEED_10000;
		phydev->duplex = DUPLEX_FULL;
	} else if (common & ADVERTISED_1000baseT_Full) {
		phydev->speed = SPEED_1000;
		phydev->duplex = DUPLEX_FULL;
	} else if (common & ADVERTISED_1000baseT_Half) {
		phydev->speed = SPEED_1000;
		phydev->duplex = DUPLEX_HALF;
	} else if (common & ADVERTISED_100baseT_Full) {
		phydev->speed = SPEED_100;
		phydev->duplex = DUPLEX_FULL;
	} else if (common & ADVERTISED_100baseT_Half) {
		phydev->speed = SPEED_100;
		phydev->duplex = DUPLEX_HALF;
	} else if (common & ADVERTISED_10baseT_Full) {
		phydev->speed = SPEED_10;
		phydev->duplex = DUPLEX_FULL;
	} else if (common & ADVERTISED_10baseT_Half) {
		phydev->speed = SPEED_10;
		phydev->duplex = DUPLEX_HALF;
	}

	if (phydev->duplex == DUPLEX_FULL) {
		phydev->pause = !!(phydev->lp_advertising & ADVERTISED_Pause);
		phydev->asym_pause = !!(phydev->lp_advertising &
					ADVERTISED_Asym_Pause);
	}
}
EXPORT_SYMBOL_GPL(phy_resolve_aneg_linkmode);

235 236
static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
			     u16 regnum)
237 238
{
	/* Write the desired MMD Devad */
239
	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
240 241

	/* Write the desired MMD register address */
242
	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
243 244

	/* Select the Function : DATA with no post increment */
245 246
	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
			devad | MII_MMD_CTRL_NOINCR);
247 248 249 250 251 252
}

/**
 * phy_read_mmd - Convenience function for reading a register
 * from an MMD on a given PHY.
 * @phydev: The phy_device struct
253 254
 * @devad: The MMD to read from (0..31)
 * @regnum: The register on the MMD to read (0..65535)
255 256 257 258 259
 *
 * Same rules as for phy_read();
 */
int phy_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
{
260 261
	int val;

262 263
	if (regnum > (u16)~0 || devad > 32)
		return -EINVAL;
264

265 266 267
	if (phydev->drv->read_mmd) {
		val = phydev->drv->read_mmd(phydev, devad, regnum);
	} else if (phydev->is_c45) {
268 269
		u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);

270 271
		val = mdiobus_read(phydev->mdio.bus, phydev->mdio.addr, addr);
	} else {
272
		struct mii_bus *bus = phydev->mdio.bus;
273
		int phy_addr = phydev->mdio.addr;
274 275

		mutex_lock(&bus->mdio_lock);
276
		mmd_phy_indirect(bus, phy_addr, devad, regnum);
277

278
		/* Read the content of the MMD's selected register */
279
		val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
280 281
		mutex_unlock(&bus->mdio_lock);
	}
282
	return val;
283
}
284
EXPORT_SYMBOL(phy_read_mmd);
285 286 287 288 289 290 291 292 293 294 295 296 297

/**
 * phy_write_mmd - Convenience function for writing a register
 * on an MMD on a given PHY.
 * @phydev: The phy_device struct
 * @devad: The MMD to read from
 * @regnum: The register on the MMD to read
 * @val: value to write to @regnum
 *
 * Same rules as for phy_write();
 */
int phy_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
{
298 299
	int ret;

300 301 302
	if (regnum > (u16)~0 || devad > 32)
		return -EINVAL;

303
	if (phydev->drv->write_mmd) {
304 305
		ret = phydev->drv->write_mmd(phydev, devad, regnum, val);
	} else if (phydev->is_c45) {
306 307
		u32 addr = MII_ADDR_C45 | (devad << 16) | (regnum & 0xffff);

308 309 310 311 312
		ret = mdiobus_write(phydev->mdio.bus, phydev->mdio.addr,
				    addr, val);
	} else {
		struct mii_bus *bus = phydev->mdio.bus;
		int phy_addr = phydev->mdio.addr;
313

314
		mutex_lock(&bus->mdio_lock);
315
		mmd_phy_indirect(bus, phy_addr, devad, regnum);
316

317
		/* Write the data into MMD's selected register */
318
		__mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
319 320 321 322 323
		mutex_unlock(&bus->mdio_lock);

		ret = 0;
	}
	return ret;
324 325
}
EXPORT_SYMBOL(phy_write_mmd);
R
Russell King 已提交
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350

/**
 * __phy_modify() - Convenience function for modifying a PHY register
 * @phydev: a pointer to a &struct phy_device
 * @regnum: register number
 * @mask: bit mask of bits to clear
 * @set: bit mask of bits to set
 *
 * Unlocked helper function which allows a PHY register to be modified as
 * new register value = (old register value & mask) | set
 */
int __phy_modify(struct phy_device *phydev, u32 regnum, u16 mask, u16 set)
{
	int ret, res;

	ret = __phy_read(phydev, regnum);
	if (ret >= 0) {
		res = __phy_write(phydev, regnum, (ret & ~mask) | set);
		if (res < 0)
			ret = res;
	}

	return ret;
}
EXPORT_SYMBOL_GPL(__phy_modify);