dcd07be3ff
Add did_interrupt() function to check if a PHY port really caused an interrupt. This is needed in the case of shared PHY interrupt pin configuration to stop interrupt event processing for PHY ports which didn't cause an interrupt. Signed-off-by: Anatolij Gustschin <agust@denx.de> Signed-off-by: David S. Miller <davem@davemloft.net>
593 lines
13 KiB
C
593 lines
13 KiB
C
/*
|
|
* drivers/net/phy/marvell.c
|
|
*
|
|
* Driver for Marvell PHYs
|
|
*
|
|
* Author: Andy Fleming
|
|
*
|
|
* Copyright (c) 2004 Freescale Semiconductor, 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/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/unistd.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mii.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/phy.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#define MII_M1011_IEVENT 0x13
|
|
#define MII_M1011_IEVENT_CLEAR 0x0000
|
|
|
|
#define MII_M1011_IMASK 0x12
|
|
#define MII_M1011_IMASK_INIT 0x6400
|
|
#define MII_M1011_IMASK_CLEAR 0x0000
|
|
|
|
#define MII_M1011_PHY_SCR 0x10
|
|
#define MII_M1011_PHY_SCR_AUTO_CROSS 0x0060
|
|
|
|
#define MII_M1145_PHY_EXT_CR 0x14
|
|
#define MII_M1145_RGMII_RX_DELAY 0x0080
|
|
#define MII_M1145_RGMII_TX_DELAY 0x0002
|
|
|
|
#define M1145_DEV_FLAGS_RESISTANCE 0x00000001
|
|
|
|
#define MII_M1111_PHY_LED_CONTROL 0x18
|
|
#define MII_M1111_PHY_LED_DIRECT 0x4100
|
|
#define MII_M1111_PHY_LED_COMBINE 0x411c
|
|
#define MII_M1111_PHY_EXT_CR 0x14
|
|
#define MII_M1111_RX_DELAY 0x80
|
|
#define MII_M1111_TX_DELAY 0x2
|
|
#define MII_M1111_PHY_EXT_SR 0x1b
|
|
|
|
#define MII_M1111_HWCFG_MODE_MASK 0xf
|
|
#define MII_M1111_HWCFG_MODE_COPPER_RGMII 0xb
|
|
#define MII_M1111_HWCFG_MODE_FIBER_RGMII 0x3
|
|
#define MII_M1111_HWCFG_MODE_SGMII_NO_CLK 0x4
|
|
#define MII_M1111_HWCFG_FIBER_COPPER_AUTO 0x8000
|
|
#define MII_M1111_HWCFG_FIBER_COPPER_RES 0x2000
|
|
|
|
#define MII_M1111_COPPER 0
|
|
#define MII_M1111_FIBER 1
|
|
|
|
#define MII_88E1121_PHY_LED_CTRL 16
|
|
#define MII_88E1121_PHY_LED_PAGE 3
|
|
#define MII_88E1121_PHY_LED_DEF 0x0030
|
|
#define MII_88E1121_PHY_PAGE 22
|
|
|
|
#define MII_M1011_PHY_STATUS 0x11
|
|
#define MII_M1011_PHY_STATUS_1000 0x8000
|
|
#define MII_M1011_PHY_STATUS_100 0x4000
|
|
#define MII_M1011_PHY_STATUS_SPD_MASK 0xc000
|
|
#define MII_M1011_PHY_STATUS_FULLDUPLEX 0x2000
|
|
#define MII_M1011_PHY_STATUS_RESOLVED 0x0800
|
|
#define MII_M1011_PHY_STATUS_LINK 0x0400
|
|
|
|
|
|
MODULE_DESCRIPTION("Marvell PHY driver");
|
|
MODULE_AUTHOR("Andy Fleming");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
static int marvell_ack_interrupt(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
/* Clear the interrupts by reading the reg */
|
|
err = phy_read(phydev, MII_M1011_IEVENT);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int marvell_config_intr(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
if (phydev->interrupts == PHY_INTERRUPT_ENABLED)
|
|
err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_INIT);
|
|
else
|
|
err = phy_write(phydev, MII_M1011_IMASK, MII_M1011_IMASK_CLEAR);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int marvell_config_aneg(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
/* The Marvell PHY has an errata which requires
|
|
* that certain registers get written in order
|
|
* to restart autonegotiation */
|
|
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1d, 0x1f);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0x200c);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1d, 0x5);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0x100);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, MII_M1011_PHY_SCR,
|
|
MII_M1011_PHY_SCR_AUTO_CROSS);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, MII_M1111_PHY_LED_CONTROL,
|
|
MII_M1111_PHY_LED_DIRECT);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = genphy_config_aneg(phydev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int m88e1121_config_aneg(struct phy_device *phydev)
|
|
{
|
|
int err, temp;
|
|
|
|
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, MII_M1011_PHY_SCR,
|
|
MII_M1011_PHY_SCR_AUTO_CROSS);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
temp = phy_read(phydev, MII_88E1121_PHY_PAGE);
|
|
|
|
phy_write(phydev, MII_88E1121_PHY_PAGE, MII_88E1121_PHY_LED_PAGE);
|
|
phy_write(phydev, MII_88E1121_PHY_LED_CTRL, MII_88E1121_PHY_LED_DEF);
|
|
phy_write(phydev, MII_88E1121_PHY_PAGE, temp);
|
|
|
|
err = genphy_config_aneg(phydev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int m88e1111_config_init(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
int temp;
|
|
|
|
/* Enable Fiber/Copper auto selection */
|
|
temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
|
|
temp &= ~MII_M1111_HWCFG_FIBER_COPPER_AUTO;
|
|
phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
|
|
|
|
temp = phy_read(phydev, MII_BMCR);
|
|
temp |= BMCR_RESET;
|
|
phy_write(phydev, MII_BMCR, temp);
|
|
|
|
if ((phydev->interface == PHY_INTERFACE_MODE_RGMII) ||
|
|
(phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) ||
|
|
(phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) ||
|
|
(phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID)) {
|
|
|
|
temp = phy_read(phydev, MII_M1111_PHY_EXT_CR);
|
|
if (temp < 0)
|
|
return temp;
|
|
|
|
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
|
|
temp |= (MII_M1111_RX_DELAY | MII_M1111_TX_DELAY);
|
|
} else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) {
|
|
temp &= ~MII_M1111_TX_DELAY;
|
|
temp |= MII_M1111_RX_DELAY;
|
|
} else if (phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) {
|
|
temp &= ~MII_M1111_RX_DELAY;
|
|
temp |= MII_M1111_TX_DELAY;
|
|
}
|
|
|
|
err = phy_write(phydev, MII_M1111_PHY_EXT_CR, temp);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
|
|
if (temp < 0)
|
|
return temp;
|
|
|
|
temp &= ~(MII_M1111_HWCFG_MODE_MASK);
|
|
|
|
if (temp & MII_M1111_HWCFG_FIBER_COPPER_RES)
|
|
temp |= MII_M1111_HWCFG_MODE_FIBER_RGMII;
|
|
else
|
|
temp |= MII_M1111_HWCFG_MODE_COPPER_RGMII;
|
|
|
|
err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
if (phydev->interface == PHY_INTERFACE_MODE_SGMII) {
|
|
temp = phy_read(phydev, MII_M1111_PHY_EXT_SR);
|
|
if (temp < 0)
|
|
return temp;
|
|
|
|
temp &= ~(MII_M1111_HWCFG_MODE_MASK);
|
|
temp |= MII_M1111_HWCFG_MODE_SGMII_NO_CLK;
|
|
|
|
err = phy_write(phydev, MII_M1111_PHY_EXT_SR, temp);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88e1118_config_aneg(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, MII_M1011_PHY_SCR,
|
|
MII_M1011_PHY_SCR_AUTO_CROSS);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = genphy_config_aneg(phydev);
|
|
return 0;
|
|
}
|
|
|
|
static int m88e1118_config_init(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
/* Change address */
|
|
err = phy_write(phydev, 0x16, 0x0002);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Enable 1000 Mbit */
|
|
err = phy_write(phydev, 0x15, 0x1070);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Change address */
|
|
err = phy_write(phydev, 0x16, 0x0003);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Adjust LED Control */
|
|
err = phy_write(phydev, 0x10, 0x021e);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/* Reset address */
|
|
err = phy_write(phydev, 0x16, 0x0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, MII_BMCR, BMCR_RESET);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88e1145_config_init(struct phy_device *phydev)
|
|
{
|
|
int err;
|
|
|
|
/* Take care of errata E0 & E1 */
|
|
err = phy_write(phydev, 0x1d, 0x001b);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0x418f);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1d, 0x0016);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0xa2da);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID) {
|
|
int temp = phy_read(phydev, MII_M1145_PHY_EXT_CR);
|
|
if (temp < 0)
|
|
return temp;
|
|
|
|
temp |= (MII_M1145_RGMII_RX_DELAY | MII_M1145_RGMII_TX_DELAY);
|
|
|
|
err = phy_write(phydev, MII_M1145_PHY_EXT_CR, temp);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (phydev->dev_flags & M1145_DEV_FLAGS_RESISTANCE) {
|
|
err = phy_write(phydev, 0x1d, 0x0012);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
temp = phy_read(phydev, 0x1e);
|
|
if (temp < 0)
|
|
return temp;
|
|
|
|
temp &= 0xf03f;
|
|
temp |= 2 << 9; /* 36 ohm */
|
|
temp |= 2 << 6; /* 39 ohm */
|
|
|
|
err = phy_write(phydev, 0x1e, temp);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1d, 0x3);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = phy_write(phydev, 0x1e, 0x8000);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* marvell_read_status
|
|
*
|
|
* Generic status code does not detect Fiber correctly!
|
|
* Description:
|
|
* Check the link, then figure out the current state
|
|
* by comparing what we advertise with what the link partner
|
|
* advertises. Start by checking the gigabit possibilities,
|
|
* then move on to 10/100.
|
|
*/
|
|
static int marvell_read_status(struct phy_device *phydev)
|
|
{
|
|
int adv;
|
|
int err;
|
|
int lpa;
|
|
int status = 0;
|
|
|
|
/* Update the link, but return if there
|
|
* was an error */
|
|
err = genphy_update_link(phydev);
|
|
if (err)
|
|
return err;
|
|
|
|
if (AUTONEG_ENABLE == phydev->autoneg) {
|
|
status = phy_read(phydev, MII_M1011_PHY_STATUS);
|
|
if (status < 0)
|
|
return status;
|
|
|
|
lpa = phy_read(phydev, MII_LPA);
|
|
if (lpa < 0)
|
|
return lpa;
|
|
|
|
adv = phy_read(phydev, MII_ADVERTISE);
|
|
if (adv < 0)
|
|
return adv;
|
|
|
|
lpa &= adv;
|
|
|
|
if (status & MII_M1011_PHY_STATUS_FULLDUPLEX)
|
|
phydev->duplex = DUPLEX_FULL;
|
|
else
|
|
phydev->duplex = DUPLEX_HALF;
|
|
|
|
status = status & MII_M1011_PHY_STATUS_SPD_MASK;
|
|
phydev->pause = phydev->asym_pause = 0;
|
|
|
|
switch (status) {
|
|
case MII_M1011_PHY_STATUS_1000:
|
|
phydev->speed = SPEED_1000;
|
|
break;
|
|
|
|
case MII_M1011_PHY_STATUS_100:
|
|
phydev->speed = SPEED_100;
|
|
break;
|
|
|
|
default:
|
|
phydev->speed = SPEED_10;
|
|
break;
|
|
}
|
|
|
|
if (phydev->duplex == DUPLEX_FULL) {
|
|
phydev->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
|
|
phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
|
|
}
|
|
} else {
|
|
int bmcr = phy_read(phydev, MII_BMCR);
|
|
|
|
if (bmcr < 0)
|
|
return bmcr;
|
|
|
|
if (bmcr & BMCR_FULLDPLX)
|
|
phydev->duplex = DUPLEX_FULL;
|
|
else
|
|
phydev->duplex = DUPLEX_HALF;
|
|
|
|
if (bmcr & BMCR_SPEED1000)
|
|
phydev->speed = SPEED_1000;
|
|
else if (bmcr & BMCR_SPEED100)
|
|
phydev->speed = SPEED_100;
|
|
else
|
|
phydev->speed = SPEED_10;
|
|
|
|
phydev->pause = phydev->asym_pause = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int m88e1121_did_interrupt(struct phy_device *phydev)
|
|
{
|
|
int imask;
|
|
|
|
imask = phy_read(phydev, MII_M1011_IEVENT);
|
|
|
|
if (imask & MII_M1011_IMASK_INIT)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct phy_driver marvell_drivers[] = {
|
|
{
|
|
.phy_id = 0x01410c60,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1101",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_aneg = &marvell_config_aneg,
|
|
.read_status = &genphy_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
{
|
|
.phy_id = 0x01410c90,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1112",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_init = &m88e1111_config_init,
|
|
.config_aneg = &marvell_config_aneg,
|
|
.read_status = &genphy_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
{
|
|
.phy_id = 0x01410cc0,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1111",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_init = &m88e1111_config_init,
|
|
.config_aneg = &marvell_config_aneg,
|
|
.read_status = &marvell_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
{
|
|
.phy_id = 0x01410e10,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1118",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_init = &m88e1118_config_init,
|
|
.config_aneg = &m88e1118_config_aneg,
|
|
.read_status = &genphy_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = {.owner = THIS_MODULE,},
|
|
},
|
|
{
|
|
.phy_id = 0x01410cb0,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1121R",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_aneg = &m88e1121_config_aneg,
|
|
.read_status = &marvell_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.did_interrupt = &m88e1121_did_interrupt,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
{
|
|
.phy_id = 0x01410cd0,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1145",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_init = &m88e1145_config_init,
|
|
.config_aneg = &marvell_config_aneg,
|
|
.read_status = &genphy_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
{
|
|
.phy_id = 0x01410e30,
|
|
.phy_id_mask = 0xfffffff0,
|
|
.name = "Marvell 88E1240",
|
|
.features = PHY_GBIT_FEATURES,
|
|
.flags = PHY_HAS_INTERRUPT,
|
|
.config_init = &m88e1111_config_init,
|
|
.config_aneg = &marvell_config_aneg,
|
|
.read_status = &genphy_read_status,
|
|
.ack_interrupt = &marvell_ack_interrupt,
|
|
.config_intr = &marvell_config_intr,
|
|
.driver = { .owner = THIS_MODULE },
|
|
},
|
|
};
|
|
|
|
static int __init marvell_init(void)
|
|
{
|
|
int ret;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++) {
|
|
ret = phy_driver_register(&marvell_drivers[i]);
|
|
|
|
if (ret) {
|
|
while (i-- > 0)
|
|
phy_driver_unregister(&marvell_drivers[i]);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit marvell_exit(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(marvell_drivers); i++)
|
|
phy_driver_unregister(&marvell_drivers[i]);
|
|
}
|
|
|
|
module_init(marvell_init);
|
|
module_exit(marvell_exit);
|