/*
* Copyright (c) 2013 Johannes Berg <johannes@sipsolutions.net>
*
* This file is free software: you may copy, redistribute 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.
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright (c) 2012 Qualcomm Atheros, Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <linux/etherdevice.h>
#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/mdio.h>
#include "reg.h"
#include "hw.h"
static inline bool alx_is_rev_a(u8 rev)
{
return rev == ALX_REV_A0 || rev == ALX_REV_A1;
}
static int alx_wait_mdio_idle(struct alx_hw *hw)
{
u32 val;
int i;
for (i = 0; i < ALX_MDIO_MAX_AC_TO; i++) {
val = alx_read_mem32(hw, ALX_MDIO);
if (!(val & ALX_MDIO_BUSY))
return 0;
udelay(10);
}
return -ETIMEDOUT;
}
static int alx_read_phy_core(struct alx_hw *hw, bool ext, u8 dev,
u16 reg, u16 *phy_data)
{
u32 val, clk_sel;
int err;
*phy_data = 0;
/* use slow clock when it's in hibernation status */
clk_sel = hw->link_speed != SPEED_UNKNOWN ?
ALX_MDIO_CLK_SEL_25MD4 :
ALX_MDIO_CLK_SEL_25MD128;
if (ext) {
val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT |
reg << ALX_MDIO_EXTN_REG_SHIFT;
alx_write_mem32(hw, ALX_MDIO_EXTN, val);
val = ALX_MDIO_SPRES_PRMBL | ALX_MDIO_START |
ALX_MDIO_MODE_EXT | ALX_MDIO_OP_READ |
clk_sel << ALX_MDIO_CLK_SEL_SHIFT;
} else {
val = ALX_MDIO_SPRES_PRMBL |
clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
reg << ALX_MDIO_REG_SHIFT |
ALX_MDIO_START | ALX_MDIO_OP_READ;
}
alx_write_mem32(hw, ALX_MDIO, val);
err = alx_wait_mdio_idle(hw);
if (err)
return err;
val = alx_read_mem32(hw, ALX_MDIO);
*phy_data = ALX_GET_FIELD(val, ALX_MDIO_DATA);
return 0;
}
static int alx_write_phy_core(struct alx_hw *hw, bool ext, u8 dev,
u16 reg, u16 phy_data)
{
u32 val, clk_sel;
/* use slow clock when it's in hibernation status */
clk_sel = hw->link_speed != SPEED_UNKNOWN ?
ALX_MDIO_CLK_SEL_25MD4 :
ALX_MDIO_CLK_SEL_25MD128;
if (ext) {
val = dev << ALX_MDIO_EXTN_DEVAD_SHIFT |
reg << ALX_MDIO_EXTN_REG_SHIFT;
alx_write_mem32(hw, ALX_MDIO_EXTN, val);
val = ALX_MDIO_SPRES_PRMBL |
clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
phy_data << ALX_MDIO_DATA_SHIFT |
ALX_MDIO_START | ALX_MDIO_MODE_EXT;
} else {
val = ALX_MDIO_SPRES_PRMBL |
clk_sel << ALX_MDIO_CLK_SEL_SHIFT |
reg << ALX_MDIO_REG_SHIFT |
phy_data << ALX_MDIO_DATA_SHIFT |
ALX_MDIO_START;
}
alx_write_mem32(hw, ALX_MDIO, val);
return alx_wait_mdio_idle(hw);
}
static int __alx_read_phy_reg(struct alx_hw *hw, u16 reg, u16 *phy_data)
{
return