|  | /* | 
|  | * Software PHY emulation | 
|  | * | 
|  | * Code taken from fixed_phy.c by Russell King <rmk+kernel@arm.linux.org.uk> | 
|  | * | 
|  | * Author: Vitaly Bordug <vbordug@ru.mvista.com> | 
|  | *         Anton Vorontsov <avorontsov@ru.mvista.com> | 
|  | * | 
|  | * Copyright (c) 2006-2007 MontaVista Software, Inc. | 
|  | * | 
|  | * 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/mii.h> | 
|  | #include <linux/phy.h> | 
|  | #include <linux/phy_fixed.h> | 
|  |  | 
|  | #include "swphy.h" | 
|  |  | 
|  | #define MII_REGS_NUM 29 | 
|  |  | 
|  | struct swmii_regs { | 
|  | u16 bmcr; | 
|  | u16 bmsr; | 
|  | u16 lpa; | 
|  | u16 lpagb; | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | SWMII_SPEED_10 = 0, | 
|  | SWMII_SPEED_100, | 
|  | SWMII_SPEED_1000, | 
|  | SWMII_DUPLEX_HALF = 0, | 
|  | SWMII_DUPLEX_FULL, | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * These two tables get bitwise-anded together to produce the final result. | 
|  | * This means the speed table must contain both duplex settings, and the | 
|  | * duplex table must contain all speed settings. | 
|  | */ | 
|  | static const struct swmii_regs speed[] = { | 
|  | [SWMII_SPEED_10] = { | 
|  | .bmcr  = BMCR_FULLDPLX, | 
|  | .lpa   = LPA_10FULL | LPA_10HALF, | 
|  | }, | 
|  | [SWMII_SPEED_100] = { | 
|  | .bmcr  = BMCR_FULLDPLX | BMCR_SPEED100, | 
|  | .bmsr  = BMSR_100FULL | BMSR_100HALF, | 
|  | .lpa   = LPA_100FULL | LPA_100HALF, | 
|  | }, | 
|  | [SWMII_SPEED_1000] = { | 
|  | .bmcr  = BMCR_FULLDPLX | BMCR_SPEED1000, | 
|  | .bmsr  = BMSR_ESTATEN, | 
|  | .lpagb = LPA_1000FULL | LPA_1000HALF, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static const struct swmii_regs duplex[] = { | 
|  | [SWMII_DUPLEX_HALF] = { | 
|  | .bmcr  = ~BMCR_FULLDPLX, | 
|  | .bmsr  = BMSR_ESTATEN | BMSR_100HALF, | 
|  | .lpa   = LPA_10HALF | LPA_100HALF, | 
|  | .lpagb = LPA_1000HALF, | 
|  | }, | 
|  | [SWMII_DUPLEX_FULL] = { | 
|  | .bmcr  = ~0, | 
|  | .bmsr  = BMSR_ESTATEN | BMSR_100FULL, | 
|  | .lpa   = LPA_10FULL | LPA_100FULL, | 
|  | .lpagb = LPA_1000FULL, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | static int swphy_decode_speed(int speed) | 
|  | { | 
|  | switch (speed) { | 
|  | case 1000: | 
|  | return SWMII_SPEED_1000; | 
|  | case 100: | 
|  | return SWMII_SPEED_100; | 
|  | case 10: | 
|  | return SWMII_SPEED_10; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * swphy_validate_state - validate the software phy status | 
|  | * @state: software phy status | 
|  | * | 
|  | * This checks that we can represent the state stored in @state can be | 
|  | * represented in the emulated MII registers.  Returns 0 if it can, | 
|  | * otherwise returns -EINVAL. | 
|  | */ | 
|  | int swphy_validate_state(const struct fixed_phy_status *state) | 
|  | { | 
|  | int err; | 
|  |  | 
|  | if (state->link) { | 
|  | err = swphy_decode_speed(state->speed); | 
|  | if (err < 0) { | 
|  | pr_warn("swphy: unknown speed\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(swphy_validate_state); | 
|  |  | 
|  | /** | 
|  | * swphy_read_reg - return a MII register from the fixed phy state | 
|  | * @reg: MII register | 
|  | * @state: fixed phy status | 
|  | * | 
|  | * Return the MII @reg register generated from the fixed phy state @state. | 
|  | */ | 
|  | int swphy_read_reg(int reg, const struct fixed_phy_status *state) | 
|  | { | 
|  | int speed_index, duplex_index; | 
|  | u16 bmsr = BMSR_ANEGCAPABLE; | 
|  | u16 bmcr = 0; | 
|  | u16 lpagb = 0; | 
|  | u16 lpa = 0; | 
|  |  | 
|  | if (reg > MII_REGS_NUM) | 
|  | return -1; | 
|  |  | 
|  | speed_index = swphy_decode_speed(state->speed); | 
|  | if (WARN_ON(speed_index < 0)) | 
|  | return 0; | 
|  |  | 
|  | duplex_index = state->duplex ? SWMII_DUPLEX_FULL : SWMII_DUPLEX_HALF; | 
|  |  | 
|  | bmsr |= speed[speed_index].bmsr & duplex[duplex_index].bmsr; | 
|  |  | 
|  | if (state->link) { | 
|  | bmsr |= BMSR_LSTATUS | BMSR_ANEGCOMPLETE; | 
|  |  | 
|  | bmcr  |= speed[speed_index].bmcr  & duplex[duplex_index].bmcr; | 
|  | lpa   |= speed[speed_index].lpa   & duplex[duplex_index].lpa; | 
|  | lpagb |= speed[speed_index].lpagb & duplex[duplex_index].lpagb; | 
|  |  | 
|  | if (state->pause) | 
|  | lpa |= LPA_PAUSE_CAP; | 
|  |  | 
|  | if (state->asym_pause) | 
|  | lpa |= LPA_PAUSE_ASYM; | 
|  | } | 
|  |  | 
|  | switch (reg) { | 
|  | case MII_BMCR: | 
|  | return bmcr; | 
|  | case MII_BMSR: | 
|  | return bmsr; | 
|  | case MII_PHYSID1: | 
|  | case MII_PHYSID2: | 
|  | return 0; | 
|  | case MII_LPA: | 
|  | return lpa; | 
|  | case MII_STAT1000: | 
|  | return lpagb; | 
|  |  | 
|  | /* | 
|  | * We do not support emulating Clause 45 over Clause 22 register | 
|  | * reads.  Return an error instead of bogus data. | 
|  | */ | 
|  | case MII_MMD_CTRL: | 
|  | case MII_MMD_DATA: | 
|  | return -1; | 
|  |  | 
|  | default: | 
|  | return 0xffff; | 
|  | } | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(swphy_read_reg); |