genode/repos/os/src/drivers/nic/gem/marvell_phy.h

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_ */