blob: c4adb3188a5992661d3e1767d12b34ae55ce7aa1 [file] [log] [blame]
/*
* Copyright (c) 2013 Qualcomm Atheros, Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* This program 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <config.h>
#include <linux/types.h>
#include <common.h>
#include <miiphy.h>
#include "phy.h"
#include <asm/addrspace.h>
#include "ar7240_soc.h"
#include "athrsf1_phy.h"
#define MODULE_NAME "ATHRSF1_PHY"
#define ATHR_LAN_PORT_VLAN 1
#define ATHR_WAN_PORT_VLAN 2
#define ENET_UNIT_LAN 1
#define ENET_UNIT_WAN 0
#define TRUE 1
#define FALSE 0
#define ATHR_PHY_MAX 5
#define ATHR_PHY0_ADDR 0x0
#define ATHR_PHY1_ADDR 0x1
#define ATHR_PHY2_ADDR 0x2
#define ATHR_PHY3_ADDR 0x3
#define ATHR_PHY4_ADDR 0x4
#define ATHR_DEBUG_PORT_ADDRESS 29
#define ATHR_DEBUG_PORT_DATA 30
/*
* Track per-PHY port information.
*/
typedef struct {
BOOL isEnetPort; /* normal enet port */
BOOL isPhyAlive; /* last known state of link */
int ethUnit; /* MAC associated with this phy port */
uint32_t phyBase;
uint32_t phyAddr; /* PHY registers associated with this phy port */
uint32_t VLANTableSetting; /* Value to be written to VLAN table */
} athrPhyInfo_t;
/*
* Per-PHY information, indexed by PHY unit number.
*/
static athrPhyInfo_t athrPhyInfo[] = {
{TRUE, /* port 1 -- LAN port 1 */
FALSE,
ENET_UNIT_LAN,
0,
ATHR_PHY0_ADDR,
ATHR_LAN_PORT_VLAN
},
{TRUE, /* port 2 -- LAN port 2 */
FALSE,
ENET_UNIT_LAN,
0,
ATHR_PHY1_ADDR,
ATHR_LAN_PORT_VLAN
},
{TRUE, /* port 3 -- LAN port 3 */
FALSE,
ENET_UNIT_LAN,
0,
ATHR_PHY2_ADDR,
ATHR_LAN_PORT_VLAN
},
{TRUE, /* port 4 -- LAN port 4 */
FALSE,
ENET_UNIT_LAN,
0,
ATHR_PHY3_ADDR,
ATHR_LAN_PORT_VLAN /* Send to all ports */
},
{TRUE, /* port 5 -- WAN Port 5 */
FALSE,
ENET_UNIT_WAN,
0,
ATHR_PHY0_ADDR,
ATHR_LAN_PORT_VLAN /* Send to all ports */
},
{FALSE, /* port 0 -- cpu port 0 */
TRUE,
ENET_UNIT_LAN,
0,
0x00,
ATHR_LAN_PORT_VLAN
},
};
#define ATHR_IS_ENET_PORT(phyUnit) (athrPhyInfo[phyUnit].isEnetPort)
#define ATHR_IS_PHY_ALIVE(phyUnit) (athrPhyInfo[phyUnit].isPhyAlive)
#define ATHR_ETHUNIT(phyUnit) (athrPhyInfo[phyUnit].ethUnit)
#define ATHR_PHYBASE(phyUnit) (athrPhyInfo[phyUnit].phyBase)
#define ATHR_PHYADDR(phyUnit) (athrPhyInfo[phyUnit].phyAddr)
#define ATHR_VLAN_TABLE_SETTING(phyUnit) (athrPhyInfo[phyUnit].VLANTableSetting)
#define ATHR_IS_ETHUNIT(phyUnit, ethUnit) \
(ATHR_IS_ENET_PORT(phyUnit) && \
ATHR_ETHUNIT(phyUnit) == (ethUnit))
#define ATHR_IS_WAN_PORT(phyUnit) (!(ATHR_ETHUNIT(phyUnit)==ENET_UNIT_LAN))
/* Forward references */
BOOL athr_phy_is_link_alive(int phyUnit);
unsigned int last_phy_speed;
void athr_enable_linkIntrs(int ethUnit)
{
return;
}
void athr_disable_linkIntrs(int ethUnit)
{
return;
}
void athr_auto_neg(int ethUnit,int phyUnit)
{
int timeout = 0;
uint16_t phyHwStatus;
if(!is_emu()) {
#if 0
phy_reg_write(ethUnit, phyUnit , ATHR_PHY_CONTROL, ATHR_CTRL_AUTONEGOTIATION_ENABLE | ATHR_CTRL_SOFTWARE_RESET);
phy_reg_write(ethUnit, phyUnit , ATHR_AUTONEG_ADVERT, ATHR_ADVERTISE_ALL);
phy_reg_write(ethUnit, phyUnit , ATHR_1000BASET_CONTROL, ATHR_ADVERTISE_1000FULL);
#endif
printf("ATHR_AUTONEG_ADVERT:%X\n",phy_reg_read(ethUnit, phyUnit,ATHR_AUTONEG_ADVERT));
printf("ATHR_1000BASET_CONTROL:%X\n",phy_reg_read(ethUnit, phyUnit,ATHR_1000BASET_CONTROL));
printf("ATHR_PHY_CONTROL:%X\n",phy_reg_read(ethUnit, phyUnit,ATHR_PHY_CONTROL));
}
else {
phy_reg_write(ethUnit, phyUnit , ATHR_AUTONEG_ADVERT, ATHR_ADVERTISE_ALL);
/* Do not advertise 1000 */
phy_reg_write(ethUnit, phyUnit , ATHR_1000BASET_CONTROL,0x0);
phy_reg_write(ethUnit, phyUnit , ATHR_PHY_CONTROL, ATHR_CTRL_AUTONEGOTIATION_ENABLE | ATHR_CTRL_SOFTWARE_RESET);
}
/*
* Wait up to 3 seconds for ALL associated PHYs to finish
* autonegotiation. The only way we get out of here sooner is
* if ALL PHYs are connected AND finish autonegotiation.
*/
timeout=20;
for (;;) {
phyHwStatus = phy_reg_read(ethUnit, phyUnit, ATHR_PHY_CONTROL);
if (ATHR_RESET_DONE(phyHwStatus)) {
printf(MODULE_NAME": Port %d, Neg Success\n", phyUnit);
break;
}
if (timeout == 0) {
printf(MODULE_NAME": Port %d, Negogiation timeout\n", phyUnit);
break;
}
if (--timeout == 0) {
printf(MODULE_NAME": Port %d, Negogiation timeout\n", phyUnit);
break;
}
mdelay(150);
}
printf(MODULE_NAME": unit %d phy addr %x ", ethUnit, phyUnit);
}
/******************************************************************************
*
* athr_phy_is_link_alive - test to see if the specified link is alive
*
* RETURNS:
* TRUE --> link is alive
* FALSE --> link is down
*/
BOOL
athr_phy_is_link_alive(int phyUnit)
{
uint16_t phyHwStatus;
uint32_t phyBase;
uint32_t phyAddr;
phyBase = ATHR_PHYBASE(phyUnit);
phyAddr = ATHR_PHYADDR(phyUnit);
phyHwStatus = phy_reg_read(0, phyAddr, ATHR_PHY_SPEC_STATUS);
if (phyHwStatus & ATHR_STATUS_LINK_PASS) {
return TRUE;
}
return FALSE;
}
/******************************************************************************
*
* athr_phy_setup - reset and setup the PHY associated with
* the specified MAC unit number.
*
* Resets the associated PHY port.
*
* RETURNS:
* TRUE --> associated PHY is alive
* FALSE --> no LINKs on this ethernet unit
*/
BOOL
athr_phy_setup(int ethUnit)
{
int phyUnit = 0;
int liveLinks = 0;
athr_auto_neg(ethUnit,phyUnit);
if (athr_phy_is_link_alive(phyUnit)) {
liveLinks++;
ATHR_IS_PHY_ALIVE(phyUnit) = TRUE;
} else {
ATHR_IS_PHY_ALIVE(phyUnit) = FALSE;
}
return (liveLinks > 0);
}
/******************************************************************************
*
* athr_phy_is_fdx - Determines whether the phy ports associated with the
* specified device are FULL or HALF duplex.
*
* RETURNS:
* 1 --> FULL
* 0 --> HALF
*/
int
athr_phy_is_fdx(int ethUnit,int phyUnit)
{
uint32_t phyBase;
uint32_t phyAddr;
uint16_t phyHwStatus;
int ii = 200;
if (athr_phy_is_link_alive(phyUnit)) {
phyBase = ATHR_PHYBASE(phyUnit);
phyAddr = ATHR_PHYADDR(phyUnit);
do {
phyHwStatus = phy_reg_read(ethUnit, ATHR_PHYADDR(phyUnit),ATHR_PHY_SPEC_STATUS);
mdelay(10);
} while((!(phyHwStatus & ATHR_STATUS_RESOVLED)) && --ii);
if (phyHwStatus & ATHER_STATUS_FULL_DUPLEX) {
return TRUE;
}
}
return FALSE;
}
/******************************************************************************
*
* athr_phy_speed - Determines the speed of phy ports associated with the
* specified device.
*
* RETURNS:
* AG7240_PHY_SPEED_10T, AG7240_PHY_SPEED_100T;
* AG7240_PHY_SPEED_1000T;
*/
int
athr_phy_speed(int ethUnit,int phyUnit)
{
uint16_t phyHwStatus;
uint32_t phyBase;
uint32_t phyAddr;
int ii = 200;
if (athr_phy_is_link_alive(phyUnit)) {
phyBase = ATHR_PHYBASE(phyUnit);
phyAddr = ATHR_PHYADDR(phyUnit);
do {
phyHwStatus = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_SPEC_STATUS);
mdelay(10);
} while((!(phyHwStatus & ATHR_STATUS_RESOVLED)) && --ii);
phyHwStatus = ((phyHwStatus & ATHER_STATUS_LINK_MASK) >>
ATHER_STATUS_LINK_SHIFT);
switch(phyHwStatus) {
case 0:
if (last_phy_speed != phyHwStatus) {
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_ADDRESS, 0x5);
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_DATA, 0x147);
last_phy_speed = phyHwStatus;
}
return _10BASET;
case 1:
if (last_phy_speed != phyHwStatus) {
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_ADDRESS, 0x5);
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_DATA, 0x147);
last_phy_speed = phyHwStatus;
}
return _100BASET;
case 2:
if (last_phy_speed != phyHwStatus) {
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_ADDRESS, 0x5);
phy_reg_write(0, phyAddr, ATHR_DEBUG_PORT_DATA, 0x0);
last_phy_speed = phyHwStatus;
}
return _1000BASET;
default:
printf("Unkown speed read!\n");
}
}
if (last_phy_speed != phyHwStatus)
{
phy_reg_write(0, ATHR_PHYADDR(phyUnit), ATHR_DEBUG_PORT_ADDRESS, 0x0);
phy_reg_write(0, ATHR_PHYADDR(phyUnit), ATHR_DEBUG_PORT_DATA, 0x14e);
last_phy_speed = phyHwStatus;
}
//printf("athr_phy_speed: link down, returning 10t\n");
return _10BASET;
}
/*****************************************************************************
*
* athr_phy_is_up -- checks for significant changes in PHY state.
*
* A "significant change" is:
* dropped link (e.g. ethernet cable unplugged) OR
* autonegotiation completed + link (e.g. ethernet cable plugged in)
*
* When a PHY is plugged in, phyLinkGained is called.
* When a PHY is unplugged, phyLinkLost is called.
*/
int
athr_phy_is_up(int ethUnit)
{
int phyUnit;
uint16_t phyHwStatus, phyHwControl;
athrPhyInfo_t *lastStatus;
int linkCount = 0;
int lostLinks = 0;
int gainedLinks = 0;
uint32_t phyBase;
uint32_t phyAddr;
for (phyUnit=0; phyUnit < 1; phyUnit++) {
phyBase = ATHR_PHYBASE(phyUnit);
phyAddr = ATHR_PHYADDR(phyUnit);
lastStatus = &athrPhyInfo[phyUnit];
if (lastStatus->isPhyAlive) { /* last known link status was ALIVE */
phyHwStatus = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_SPEC_STATUS);
/* See if we've lost link */
if (phyHwStatus & ATHR_STATUS_LINK_PASS) { /* check realtime link */
linkCount++;
} else {
phyHwStatus = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_STATUS);
/* If realtime failed check link in latch register before
* asserting link down.
*/
if (phyHwStatus & ATHR_LATCH_LINK_PASS)
linkCount++;
else
lostLinks++;
lastStatus->isPhyAlive = FALSE;
}
} else { /* last known link status was DEAD */
/* Check for reset complete */
phyHwStatus = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_STATUS);
if (!ATHR_RESET_DONE(phyHwStatus))
continue;
phyHwControl = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_CONTROL);
/* Check for AutoNegotiation complete */
if ((!(phyHwControl & ATHR_CTRL_AUTONEGOTIATION_ENABLE))
|| ATHR_AUTONEG_DONE(phyHwStatus)) {
phyHwStatus = phy_reg_read(0, ATHR_PHYADDR(phyUnit),ATHR_PHY_SPEC_STATUS);
if (phyHwStatus & ATHR_STATUS_LINK_PASS) {
gainedLinks++;
linkCount++;
lastStatus->isPhyAlive = TRUE;
}
}
}
}
return (linkCount);
}
/* Place holders */
int
athr_reg_init(void *arg)
{
return 0;
}