580 lines
15 KiB
C++
580 lines
15 KiB
C++
/*
|
|
* \brief Phy driver for Marvell chips
|
|
* \author Johannes Schlatow
|
|
* \author Timo Wischer
|
|
* \date 2015-05-11
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2015 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifndef _INCLUDE__DRIVERS__NIC__GEM__MARVELL_PHY_H_
|
|
#define _INCLUDE__DRIVERS__NIC__GEM__MARVELL_PHY_H_
|
|
|
|
/* Genode includes */
|
|
#include <os/attached_mmio.h>
|
|
#include <nic_session/nic_session.h>
|
|
#include <nic/driver.h>
|
|
#include <timer_session/connection.h>
|
|
|
|
#include "phyio.h"
|
|
|
|
namespace Genode
|
|
{
|
|
|
|
enum Eth_speed {
|
|
UNDEFINED,
|
|
SPEED_10 = 10,
|
|
SPEED_100 = 100,
|
|
SPEED_1000 = 1000
|
|
};
|
|
|
|
class MII_phy
|
|
{
|
|
protected:
|
|
/**
|
|
* \param _OFFSET Offset/number of the register.
|
|
*
|
|
* For further details See 'Genode::Register'.
|
|
*/
|
|
template <uint8_t _OFFSET>
|
|
struct Phy_register : public Genode::Register<16>
|
|
{
|
|
enum {
|
|
OFFSET = _OFFSET,
|
|
};
|
|
|
|
typedef Phy_register<_OFFSET>
|
|
Register_base;
|
|
|
|
/**
|
|
* A region within a register
|
|
*
|
|
* \param _SHIFT Bit shift of the first bit within the
|
|
* compound register.
|
|
* \param _WIDTH bit width of the region
|
|
*
|
|
* For details see 'Genode::Register::Bitfield'.
|
|
*/
|
|
template <unsigned long _SHIFT, unsigned long _WIDTH>
|
|
struct Bitfield : public Genode::Register<16>::
|
|
template Bitfield<_SHIFT, _WIDTH>
|
|
{
|
|
typedef Bitfield<_SHIFT, _WIDTH> Bitfield_base;
|
|
|
|
/* back reference to containing register */
|
|
typedef Phy_register<_OFFSET>
|
|
Compound_reg;
|
|
};
|
|
};
|
|
|
|
/*************************
|
|
* Generic MII registers *
|
|
*************************/
|
|
|
|
/* Basic mode control register */
|
|
struct Bmcr : Phy_register<0x00>
|
|
{
|
|
struct Resv : Bitfield<0, 6> { }; /* Unused... */
|
|
struct Speed1000 : Bitfield<6, 1> { }; /* MSB of Speed (1000) */
|
|
struct Ctst : Bitfield<7, 1> { }; /* Collision test */
|
|
struct Fulldplx : Bitfield<8, 1> { }; /* Full duplex */
|
|
struct Anrestart : Bitfield<9, 1> { }; /* Auto negotiation restart */
|
|
struct Isolate : Bitfield<10,1> { }; /* Disconnect DP83840 from MII */
|
|
struct Pdown : Bitfield<11,1> { }; /* Powerdown the DP83840 */
|
|
struct Anenable : Bitfield<12,1> { }; /* Enable auto negotiation */
|
|
struct Speed100 : Bitfield<13,1> { }; /* Select 100Mbps */
|
|
struct Loopback : Bitfield<14,1> { }; /* TXD loopback bits */
|
|
struct Reset : Bitfield<15,1> { }; /* Reset the DP83840 */
|
|
};
|
|
|
|
/* Basic mode status register */
|
|
struct Bmsr : Phy_register<0x01>
|
|
{
|
|
enum {
|
|
INVALID = 0xFFFF
|
|
};
|
|
|
|
struct Ercap : Bitfield<0, 1> { }; /* Ext-reg capability */
|
|
struct Jcd : Bitfield<1, 1> { }; /* Jabber detected */
|
|
struct Lstatus : Bitfield<2, 1> { }; /* Link status */
|
|
struct Anegcapable : Bitfield<3, 1> { }; /* Able to do auto-negotiation */
|
|
struct Rfault : Bitfield<4, 1> { }; /* Remote fault detected */
|
|
struct Anegcomplete: Bitfield<5, 1> { }; /* Auto-negotiation complete */
|
|
struct Resv : Bitfield<6, 1> { }; /* Unused... */
|
|
struct Estaten : Bitfield<7, 1> { }; /* Extended Status in R15 */
|
|
struct Half2_100 : Bitfield<8, 1> { }; /* Can do 100BASE-T2 HDX */
|
|
struct Full2_100 : Bitfield<9, 1> { }; /* Can do 100BASE-T2 FDX */
|
|
struct Half_10 : Bitfield<10,1> { }; /* Can do 10mbps, half-duplex */
|
|
struct Full_10 : Bitfield<11,1> { }; /* Can do 10mbps, full-duplex */
|
|
struct Half_100 : Bitfield<12,1> { }; /* Can do 100mbps, half-duplex */
|
|
struct Full_100 : Bitfield<13,1> { }; /* Can do 100mbps, full-duplex */
|
|
struct Base4_100 : Bitfield<14,1> { }; /* Can do 100mbps, 4k packets */
|
|
};
|
|
|
|
/* ID register 1 */
|
|
struct Idr1 : Phy_register<0x02> { };
|
|
|
|
/* ID register 2 */
|
|
struct Idr2 : Phy_register<0x03> { };
|
|
|
|
/* Advertisement control reg */
|
|
struct Advertise : Phy_register<0x04>
|
|
{
|
|
struct Csma : Bitfield<0, 1> { };
|
|
struct Half_10 : Bitfield<5, 1> { }; /* Try for 10mbps half-duplex */
|
|
struct FullX_1000 : Bitfield<5, 1> { }; /* Try for 1000BASE-X full-duplex */
|
|
struct Full_10 : Bitfield<6, 1> { }; /* Try for 10mbps full-duplex */
|
|
struct HalfX_1000 : Bitfield<6, 1> { }; /* Try for 1000BASE-X half-duplex */
|
|
struct Half_100 : Bitfield<7, 1> { }; /* Try for 100mbps half-duplex */
|
|
struct PauseX_1000 : Bitfield<7, 1> { }; /* Try for 1000BASE-X pause */
|
|
struct Full_100 : Bitfield<8, 1> { }; /* Try for 100mbps full-duplex */
|
|
struct AsymXPSE_1000: Bitfield<8, 1> { }; /* Try for 1000BASE-X asym pause */
|
|
struct Base4_100 : Bitfield<9, 1> { }; /* Try for 100mbps 4k packets */
|
|
struct Pause_cap : Bitfield<10,1> { }; /* Try for pause */
|
|
struct Pause_asym : Bitfield<11,1> { }; /* Try for asymetrict pause */
|
|
struct Rfault : Bitfield<13,1> { }; /* Say we can detect faults */
|
|
struct Lpack : Bitfield<14,1> { }; /* Ack link partners response */
|
|
struct Npage : Bitfield<15,1> { }; /* Next page bit */
|
|
};
|
|
|
|
/* 1000BASE-T control */
|
|
struct Ctrl1000 : Phy_register<0x09>
|
|
{
|
|
struct Half_1000 : Bitfield<8, 1> { }; /* Advertise 1000BASE-T full duplex */
|
|
struct Full_1000 : Bitfield<9, 1> { }; /* Advertise 1000BASE-T half duplex */
|
|
};
|
|
};
|
|
|
|
class Marvel_phy : public MII_phy
|
|
{
|
|
public:
|
|
class Phy_timeout_after_reset : public Genode::Exception {};
|
|
|
|
|
|
private:
|
|
|
|
enum {
|
|
PHY_AUTONEGOTIATE_TIMEOUT = 5000
|
|
};
|
|
|
|
Timer::Connection _timer;
|
|
Phyio& _phyio;
|
|
uint8_t _phyaddr;
|
|
bool _link_up;
|
|
Eth_speed _eth_speed;
|
|
|
|
|
|
/*************************
|
|
* 88E1310 PHY registers *
|
|
*************************/
|
|
|
|
struct Led_ctrl : Phy_register<16>
|
|
{
|
|
struct Data : Bitfield<0, 4> { };
|
|
};
|
|
|
|
struct Irq_en : Phy_register<18> { };
|
|
|
|
struct RGMII_ctrl : Phy_register<21> { };
|
|
|
|
struct Page_select : Phy_register<22> { };
|
|
|
|
/* 88E1011 PHY Status Register */
|
|
struct Phy_stat : Phy_register<0x11>
|
|
{
|
|
struct Link : Bitfield<10,1> { };
|
|
struct Spddone : Bitfield<11,1> { };
|
|
struct Duplex : Bitfield<13,1> { };
|
|
struct Speed_100 : Bitfield<14,1> { };
|
|
struct Speed_1000 : Bitfield<15,1> { };
|
|
};
|
|
|
|
/**
|
|
* Read register of detected PHY.
|
|
*/
|
|
inline uint16_t _phy_read(const uint8_t regnum) const
|
|
{
|
|
uint16_t val;
|
|
_phyio.phy_read(_phyaddr, regnum, val);
|
|
return val;
|
|
}
|
|
|
|
/**
|
|
* Write register of detected PHY.
|
|
*/
|
|
inline void _phy_write(const uint8_t regnum, const uint16_t data) const
|
|
{
|
|
_phyio.phy_write(_phyaddr, regnum, data);
|
|
}
|
|
|
|
/**
|
|
* Read PHY register 'T'
|
|
*/
|
|
template <typename T>
|
|
inline typename T::Register_base::access_t phy_read() const
|
|
{
|
|
typedef typename T::Register_base Register;
|
|
return _phy_read(Register::OFFSET);
|
|
}
|
|
|
|
/**
|
|
* Read the bitfield 'T' of PHY register
|
|
*/
|
|
template <typename T>
|
|
inline typename T::Bitfield_base::Compound_reg::access_t
|
|
phy_read() const
|
|
{
|
|
typedef typename T::Bitfield_base Bitfield;
|
|
typedef typename Bitfield::Compound_reg Register;
|
|
return Bitfield::get(_phy_read(Register::OFFSET));
|
|
}
|
|
|
|
/**
|
|
* Write PHY register 'T'
|
|
*/
|
|
template <typename T>
|
|
inline void phy_write(uint16_t const val) const
|
|
{
|
|
typedef typename T::Register_base Register;
|
|
return _phy_write(Register::OFFSET, val);
|
|
}
|
|
|
|
/**
|
|
* Detect PHY address.
|
|
*/
|
|
void phy_detection()
|
|
{
|
|
/* check _phyaddr */
|
|
if (_phyaddr != -1) {
|
|
Bmsr::access_t phyreg = phy_read<Bmsr>();
|
|
if ((phyreg != Bmsr::INVALID) &&
|
|
Bmsr::Full_10::get(phyreg) &&
|
|
Bmsr::Anegcapable::get(phyreg)) {
|
|
/* Found a valid PHY address */
|
|
PDBG("Default phy address %d is valid\n", _phyaddr);
|
|
return;
|
|
} else {
|
|
PDBG("PHY address is not setup correctly %d\n", _phyaddr);
|
|
_phyaddr = -1;
|
|
}
|
|
}
|
|
|
|
PDBG("detecting phy address\n");
|
|
if (_phyaddr == -1) {
|
|
/* detect the PHY address */
|
|
for (int i = 31; i >= 0; i--) {
|
|
_phyaddr = i;
|
|
Bmsr::access_t phyreg = phy_read<Bmsr>();
|
|
if ((phyreg != Bmsr::INVALID) &&
|
|
Bmsr::Full_10::get(phyreg) &&
|
|
Bmsr::Anegcapable::get(phyreg)) {
|
|
/* Found a valid PHY address */
|
|
PDBG("Found valid phy address, %d\n", i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
PDBG("PHY is not detected\n");
|
|
_phyaddr = -1;
|
|
}
|
|
|
|
uint32_t get_phy_id()
|
|
{
|
|
uint32_t phy_id = 0;
|
|
|
|
/* Grab the bits from PHYIR1, and put them
|
|
* in the upper half */
|
|
phy_id = phy_read<Idr1>() << 16;
|
|
|
|
/* Grab the bits from PHYIR2, and put them in the lower half */
|
|
phy_id |= phy_read<Idr2>();
|
|
|
|
return phy_id;
|
|
}
|
|
|
|
void m88e1310_config()
|
|
{
|
|
/* LED link and activity */
|
|
phy_write<Page_select>(0x0003);
|
|
Led_ctrl::access_t led = phy_read<Led_ctrl>();
|
|
Led_ctrl::Data::set(led, 0x1);
|
|
phy_write<Led_ctrl>(led);
|
|
|
|
/* Set LED2/INT to INT mode, low active */
|
|
phy_write<Page_select>(0x0003);
|
|
Irq_en::access_t irq = phy_read<Irq_en>();
|
|
irq = (irq & 0x77ff) | 0x0880;
|
|
phy_write<Irq_en>(irq);
|
|
|
|
/* Set RGMII delay */
|
|
phy_write<Page_select>(0x0002);
|
|
RGMII_ctrl::access_t ctrl = phy_read<RGMII_ctrl>();
|
|
ctrl |= 0x0030;
|
|
phy_write<RGMII_ctrl>(ctrl);
|
|
|
|
/* Ensure to return to page 0 */
|
|
phy_write<Page_select>(0x0000);
|
|
|
|
genphy_config_aneg();
|
|
phy_reset();
|
|
}
|
|
|
|
int genphy_config_aneg()
|
|
{
|
|
/**
|
|
* Description: If auto-negotiation is enabled, we configure the
|
|
* advertising, and then restart auto-negotiation. If it is not
|
|
* enabled, then we write the BMCR.
|
|
*/
|
|
int result = genphy_config_advert();
|
|
|
|
if (result < 0) /* error */
|
|
return result;
|
|
|
|
if (result == 0) {
|
|
PDBG("Config not changed");
|
|
/* Advertisment hasn't changed, but maybe aneg was never on to
|
|
* begin with? Or maybe phy was isolated? */
|
|
uint16_t ctl = phy_read<Bmcr>();
|
|
|
|
if (!Bmcr::Anenable::get(ctl) || Bmcr::Isolate::get(ctl))
|
|
result = 1; /* do restart aneg */
|
|
} else {
|
|
PDBG("Config changed");
|
|
}
|
|
|
|
/* Only restart aneg if we are advertising something different
|
|
* than we were before. */
|
|
if (result > 0)
|
|
result = genphy_restart_aneg();
|
|
|
|
return result;
|
|
}
|
|
|
|
int genphy_config_advert()
|
|
{
|
|
/**
|
|
* Description: Writes MII_ADVERTISE with the appropriate values,
|
|
* after sanitizing the values to make sure we only advertise
|
|
* what is supported. Returns < 0 on error, 0 if the PHY's advertisement
|
|
* hasn't changed, and > 0 if it has changed.
|
|
*/
|
|
int changed = 0;
|
|
|
|
/* Setup standard advertisement */
|
|
Advertise::access_t adv = phy_read<Advertise>();
|
|
|
|
Advertise::access_t oldadv = adv;
|
|
|
|
Advertise::Base4_100::set(adv, 0);
|
|
Advertise::Pause_cap::set(adv, 1);
|
|
Advertise::Pause_asym::set(adv, 1);
|
|
Advertise::Half_10::set(adv, 1);
|
|
Advertise::Full_10::set(adv, 1);
|
|
Advertise::Half_100::set(adv, 1);
|
|
Advertise::Full_100::set(adv, 1);
|
|
|
|
if (adv != oldadv) {
|
|
phy_write<Advertise>(adv);
|
|
changed = 1;
|
|
}
|
|
|
|
/* Configure gigabit if it's supported */
|
|
adv = phy_read<Ctrl1000>();
|
|
|
|
oldadv = adv;
|
|
|
|
Ctrl1000::Full_1000::set(adv, 1);
|
|
Ctrl1000::Half_1000::set(adv, 1);
|
|
|
|
if (adv != oldadv) {
|
|
phy_write<Ctrl1000>(adv);
|
|
changed = 1;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
int genphy_restart_aneg()
|
|
{
|
|
Bmcr::access_t ctl = phy_read<Bmcr>();
|
|
Bmcr::Anenable::set(ctl, 1);
|
|
Bmcr::Anrestart::set(ctl, 1);
|
|
|
|
/* Don't isolate the PHY if we're negotiating */
|
|
Bmcr::Isolate::set(ctl, 0);
|
|
|
|
phy_write<Bmcr>(ctl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int phy_reset()
|
|
{
|
|
int timeout = 500;
|
|
|
|
Bmcr::access_t reg = phy_read<Bmcr>();
|
|
Bmcr::Reset::set(reg, 1);
|
|
phy_write<Bmcr>(reg);
|
|
|
|
/*
|
|
* Poll the control register for the reset bit to go to 0 (it is
|
|
* auto-clearing). This should happen within 0.5 seconds per the
|
|
* IEEE spec.
|
|
*/
|
|
while (phy_read<Bmcr::Reset>() && timeout--) {
|
|
_timer.msleep(1);
|
|
}
|
|
|
|
if (phy_read<Bmcr::Reset>()) {
|
|
PWRN("PHY reset timed out\n");
|
|
throw Phy_timeout_after_reset();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int m88e1011s_startup()
|
|
{
|
|
genphy_update_link();
|
|
m88e1xxx_parse_status();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int genphy_update_link()
|
|
{
|
|
/**
|
|
* Description: Update the value in phydev->link to reflect the
|
|
* current link value. In order to do this, we need to read
|
|
* the status register twice, keeping the second value.
|
|
*/
|
|
|
|
/*
|
|
* Wait if the link is up, and autonegotiation is in progress
|
|
* (ie - we're capable and it's not done)
|
|
*/
|
|
Bmsr::access_t mii_reg = phy_read<Bmsr>();
|
|
|
|
/*
|
|
* If we already saw the link up, and it hasn't gone down, then
|
|
* we don't need to wait for autoneg again
|
|
*/
|
|
if (_link_up && Bmsr::Lstatus::get(mii_reg))
|
|
return 0;
|
|
|
|
if ( Bmsr::Anegcapable::get(mii_reg) && !Bmsr::Anegcomplete::get(mii_reg) ) {
|
|
int i = 0;
|
|
|
|
Genode::printf("Waiting for PHY auto negotiation to complete");
|
|
while (!Bmsr::Anegcomplete::get(mii_reg)) {
|
|
/*
|
|
* Timeout reached ?
|
|
*/
|
|
if (i > PHY_AUTONEGOTIATE_TIMEOUT) {
|
|
PWRN(" TIMEOUT !\n");
|
|
_link_up = false;
|
|
return 0;
|
|
}
|
|
|
|
if ((i++ % 500) == 0)
|
|
Genode::printf(".");
|
|
_timer.msleep(1);
|
|
|
|
mii_reg = phy_read<Bmsr>();
|
|
}
|
|
Genode::printf(" done\n");
|
|
_link_up = true;
|
|
} else {
|
|
/* Read the link a second time to clear the latched state */
|
|
mii_reg = phy_read<Bmsr>();
|
|
|
|
if (Bmsr::Lstatus::get(mii_reg))
|
|
_link_up = true;
|
|
else
|
|
_link_up = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int m88e1xxx_parse_status()
|
|
{
|
|
/**
|
|
* Parse the 88E1011's status register for speed and duplex
|
|
* information
|
|
*/
|
|
Phy_stat::access_t stat = phy_read<Phy_stat>();
|
|
|
|
if ( Phy_stat::Link::get(stat) &&
|
|
!Phy_stat::Spddone::get(stat)) {
|
|
int i = 0;
|
|
|
|
PDBG("Waiting for PHY realtime link");
|
|
while (!phy_read<Phy_stat::Spddone>()) {
|
|
/* Timeout reached ? */
|
|
if (i > PHY_AUTONEGOTIATE_TIMEOUT) {
|
|
PWRN(" TIMEOUT !");
|
|
_link_up = false;
|
|
break;
|
|
}
|
|
|
|
if ((i++ % 1000) == 0)
|
|
Genode::printf(".");
|
|
_timer.msleep(1);
|
|
}
|
|
PINF(" done");
|
|
_timer.msleep(500);
|
|
} else {
|
|
if (Phy_stat::Link::get(stat))
|
|
_link_up = true;
|
|
else
|
|
_link_up = false;
|
|
}
|
|
|
|
// TODO change emac configuration if half duplex
|
|
|
|
if (Phy_stat::Speed_1000::get(stat))
|
|
_eth_speed = SPEED_1000;
|
|
else if (Phy_stat::Speed_100::get(stat))
|
|
_eth_speed = SPEED_100;
|
|
else
|
|
_eth_speed = SPEED_10;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
public:
|
|
Marvel_phy(Phyio& phyio) :
|
|
_phyio(phyio),
|
|
_phyaddr(0),
|
|
_link_up(false),
|
|
_eth_speed(UNDEFINED)
|
|
{ }
|
|
|
|
void init()
|
|
{
|
|
phy_detection();
|
|
|
|
uint32_t phy_id = get_phy_id();
|
|
PDBG("The found phy has the id %08x", phy_id);
|
|
|
|
phy_reset();
|
|
m88e1310_config();
|
|
m88e1011s_startup();
|
|
}
|
|
|
|
Eth_speed eth_speed() { return _eth_speed; }
|
|
|
|
};
|
|
}
|
|
|
|
#endif /* _INCLUDE__DRIVERS__NIC__GEM__MARVELL_PHY_H_ */
|
|
|