* Stefano Ceccherini <stefano.ceccherini@gmail.com>. All rights reserved.
* This file is released under the MIT license
*/
#include "device.h"
#include "driver.h"
#include "debug.h"
#include "ether_driver.h"
#include "interface.h"
#include "wb840.h"
#include <ByteOrder.h>
#include <KernelExport.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define ROUND_TO_PAGE_SIZE(x) (((x) + (B_PAGE_SIZE) - 1) & ~((B_PAGE_SIZE) - 1))
#define PHY_ID0_DAVICOM_DM9101 0x0181
#define PHY_ID1_DAVICOM_DM9101 0xb800
#define MII_HOME 0x0001
#define MII_LAN 0x0002
struct mii_chip_info
{
const char* name;
uint16 id0;
uint16 id1;
uint8 types;
};
const static struct mii_chip_info
gMIIChips[] = {
{"DAVICOM_DM9101", PHY_ID0_DAVICOM_DM9101, PHY_ID1_DAVICOM_DM9101, MII_LAN},
{NULL, 0, 0, 0}
};
static int
mii_readstatus(wb_device* device)
{
int i = 0;
int status;
while (i++ < 2)
status = wb_miibus_readreg(device, device->phy, MII_STATUS);
return status;
}
static phys_addr_t
physicalAddress(volatile void* addr, uint32 length)
{
physical_entry table;
get_memory_map((void*)addr, length, &table, 1);
return table.address;
}
void
wb_put_rx_descriptor(volatile wb_desc* descriptor)
{
descriptor->wb_status = WB_RXSTAT_OWN;
descriptor->wb_ctl |= WB_MAX_FRAMELEN | WB_RXCTL_RLINK;
}
void
wb_enable_interrupts(wb_device* device)
{
write32(device->reg_base + WB_IMR, WB_INTRS);
write32(device->reg_base + WB_ISR, 0xFFFFFFFF);
}
void
wb_disable_interrupts(wb_device* device)
{
write32(device->reg_base + WB_IMR, 0L);
write32(device->reg_base + WB_ISR, 0L);
}
static void
wb_selectPHY(wb_device* device)
{
uint16 status;
device->currentPHY = device->firstPHY;
device->phy = device->currentPHY->address;
status = wb_miibus_readreg(device, device->phy, MII_CONTROL);
status &= ~MII_CONTROL_ISOLATE;
wb_miibus_writereg(device, device->phy, MII_CONTROL, status);
wb_read_mode(device);
}
status_t
wb_initPHYs(wb_device* device)
{
uint16 phy;
for (phy = 0; phy < 32; phy++) {
struct mii_phy* mii;
uint16 status;
int i = 0;
status = wb_miibus_readreg(device, phy, MII_STATUS);
status = wb_miibus_readreg(device, phy, MII_STATUS);
if (status == 0xffff || status == 0x0000)
continue;
mii = (struct mii_phy*)calloc(1, sizeof(struct mii_phy));
if (mii == NULL)
return B_NO_MEMORY;
mii->address = phy;
mii->id0 = wb_miibus_readreg(device, phy, MII_PHY_ID0);
mii->id1 = wb_miibus_readreg(device, phy, MII_PHY_ID1);
mii->types = MII_HOME;
mii->next = device->firstPHY;
device->firstPHY = mii;
while (gMIIChips[i].name != NULL) {
if (gMIIChips[i].id0 == mii->id0
&& gMIIChips[i].id1 == (mii->id1 & 0xfff0)) {
dprintf("Found MII PHY: %s\n", gMIIChips[i].name);
mii->types = gMIIChips[i].types;
break;
}
i++;
}
if (gMIIChips[i].name == NULL) {
dprintf("Unknown MII PHY transceiver: id = (%x, %x).\n",
mii->id0, mii->id1);
}
}
if (device->firstPHY == NULL) {
dprintf("No MII PHY transceiver found!\n");
return B_ENTRY_NOT_FOUND;
}
wb_selectPHY(device);
device->link = mii_readstatus(device) & MII_STATUS_LINK;
return B_OK;
}
void
wb_init(wb_device* device)
{
LOG((DEVICE_NAME": init()\n"));
wb_reset(device);
device->wb_txthresh = WB_TXTHRESH_INIT;
switch(device->wb_cachesize) {
case 32:
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_32LONG);
break;
case 16:
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_16LONG);
break;
case 8:
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_8LONG);
break;
case 0:
default:
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_CACHEALIGN_NONE);
break;
}
write32(device->reg_base + WB_BUSCTL,
WB_BUSCTL_MUSTBEONE | WB_BUSCTL_ARBITRATION);
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BURSTLEN_16LONG);
write32(device->reg_base + WB_BUSCTL_SKIPLEN, WB_SKIPLEN_4LONG);
WB_CLRBIT(device->reg_base + WB_NETCFG,
(WB_NETCFG_TX_EARLY_ON | WB_NETCFG_RX_EARLY_ON));
wb_set_rx_filter(device);
}
void
wb_reset(wb_device *device)
{
int i = 0;
LOG((DEVICE_NAME": reset()\n"));
write32(device->reg_base + WB_NETCFG, 0L);
write32(device->reg_base + WB_BUSCTL, 0L);
write32(device->reg_base + WB_TXADDR, 0L);
write32(device->reg_base + WB_RXADDR, 0L);
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BUSCTL_RESET);
WB_SETBIT(device->reg_base + WB_BUSCTL, WB_BUSCTL_RESET);
for (i = 0; i < WB_TIMEOUT; i++) {
if (!(read32(device->reg_base + WB_BUSCTL) & WB_BUSCTL_RESET))
break;
}
if (i == WB_TIMEOUT)
LOG((DEVICE_NAME": reset hasn't completed!!!"));
snooze(1000);
}
status_t
wb_stop(wb_device* device)
{
uint32 cfgAddress = (uint32)device->reg_base + WB_NETCFG;
int32 i = 0;
if (read32(cfgAddress) & (WB_NETCFG_TX_ON | WB_NETCFG_RX_ON)) {
WB_CLRBIT(cfgAddress, (WB_NETCFG_TX_ON | WB_NETCFG_RX_ON));
for (i = 0; i < WB_TIMEOUT; i++) {
if ((read32(device->reg_base + WB_ISR) & WB_ISR_TX_IDLE) &&
(read32(device->reg_base + WB_ISR) & WB_ISR_RX_IDLE))
break;
}
}
if (i < WB_TIMEOUT)
return B_OK;
return B_ERROR;
}
static void
wb_updateLink(wb_device* device)
{
if (!device->autoNegotiationComplete) {
int32 mode = wb_read_mode(device);
if (mode)
wb_set_mode(device, mode);
return;
}
if (device->link) {
uint16 status = mii_readstatus(device);
if ((status & MII_STATUS_LINK) == 0)
device->link = false;
} else {
uint16 status;
wb_selectPHY(device);
status = mii_readstatus(device);
if (status & MII_STATUS_LINK)
device->link = true;
}
}
int32
wb_tick(timer* arg)
{
wb_device* device = (wb_device*)arg;
wb_updateLink(device);
return B_HANDLED_INTERRUPT;
}
* Program the rx filter.
*/
void
wb_set_rx_filter(wb_device* device)
{
WB_SETBIT(device->reg_base + WB_NETCFG, WB_NETCFG_RX_BROAD);
}
static status_t
wb_rxok(wb_device* device)
{
uint32 releaseRxSem = 0;
int16 limit;
acquire_spinlock(&device->rxSpinlock);
for (limit = device->rxFree; limit > 0; limit--) {
if (device->rxDescriptor[device->rxInterruptIndex].wb_status
& WB_RXSTAT_OWN) {
break;
}
releaseRxSem++;
device->rxInterruptIndex = (device->rxInterruptIndex + 1)
& WB_RX_CNT_MASK;
device->rxFree--;
}
write32(device->reg_base + WB_RXSTART, 0xFFFFFFFF);
release_spinlock(&device->rxSpinlock);
if (releaseRxSem > 0) {
release_sem_etc(device->rxSem, releaseRxSem, B_DO_NOT_RESCHEDULE);
return B_INVOKE_SCHEDULER;
}
return B_HANDLED_INTERRUPT;
}
static status_t
wb_tx_nobuf(wb_device* info)
{
int16 releaseTxSem = 0;
int16 limit;
status_t status;
acquire_spinlock(&info->txSpinlock);
for (limit = info->txSent; limit > 0; limit--) {
status = info->txDescriptor[info->txInterruptIndex].wb_status;
LOG(("wb_tx_nobuf, status: %lx\n", status));
if (status & WB_TXSTAT_TXERR) {
LOG(("TX_STAT_ERR\n"));
break;
} else if (status & WB_UNSENT) {
LOG(("TX_STAT_UNSENT\n"));
break;
} else if (status & WB_TXSTAT_OWN) {
LOG((DEVICE_NAME": Device still owns the descriptor\n"));
break;
} else
info->txDescriptor[info->txInterruptIndex].wb_status = 0;
releaseTxSem++;
info->txInterruptIndex = (info->txInterruptIndex + 1) & WB_TX_CNT_MASK;
info->txSent--;
if (info->txSent < 0 || info->txSent > WB_TX_LIST_CNT)
dprintf("ERROR interrupt: txSent = %d\n", info->txSent);
}
release_spinlock(&info->txSpinlock);
if (releaseTxSem) {
release_sem_etc(info->txSem, releaseTxSem, B_DO_NOT_RESCHEDULE);
return B_INVOKE_SCHEDULER;
}
return B_HANDLED_INTERRUPT;
}
int32
wb_interrupt(void* arg)
{
wb_device* device = (wb_device*)arg;
int32 retval = B_UNHANDLED_INTERRUPT;
uint32 status;
acquire_spinlock(&device->intLock);
status = read32(device->reg_base + WB_ISR);
if (status & WB_INTRS) {
if (status)
write32(device->reg_base + WB_ISR, status);
if (status & WB_ISR_ABNORMAL)
LOG((DEVICE_NAME": *** Abnormal Interrupt received ***\n"));
else
LOG((DEVICE_NAME": interrupt received: \n"));
if (status & WB_ISR_RX_EARLY) {
LOG(("WB_ISR_RX_EARLY\n"));
}
if (status & WB_ISR_RX_NOBUF) {
LOG(("WB_ISR_RX_NOBUF\n"));
}
if (status & WB_ISR_RX_ERR) {
LOG(("WB_ISR_RX_ERR\n"));
}
if (status & WB_ISR_RX_OK) {
LOG(("WB_ISR_RX_OK\n"));
retval = wb_rxok(device);
}
if (status & WB_ISR_RX_IDLE) {
LOG(("WB_ISR_RX_IDLE\n"));
}
if (status & WB_ISR_TX_EARLY) {
LOG(("WB_ISR_TX_EARLY\n"));
}
if (status & WB_ISR_TX_NOBUF) {
LOG(("WB_ISR_TX_NOBUF\n"));
retval = wb_tx_nobuf(device);
}
if (status & WB_ISR_TX_UNDERRUN) {
LOG(("WB_ISR_TX_UNDERRUN\n"));
}
if (status & WB_ISR_TX_IDLE) {
LOG(("WB_ISR_TX_IDLE\n"));
}
if (status & WB_ISR_TX_OK) {
LOG(("WB_ISR_TX_OK\n"));
}
if (status & WB_ISR_BUS_ERR) {
LOG(("WB_ISR_BUS_ERROR: %lx\n", (status & WB_ISR_BUSERRTYPE) >> 4));
}
if (status & WB_ISR_TIMER_EXPIRED) {
LOG(("WB_ISR_TIMER_EXPIRED\n"));
}
}
release_spinlock(&device->intLock);
return retval;
}
* Print an ethernet address
*/
void
print_address(ether_address_t* addr)
{
int i;
char buf[3 * 6 + 1];
for (i = 0; i < 5; i++) {
sprintf(&buf[3 * i], "%02x:", addr->ebyte[i]);
}
sprintf(&buf[3 * 5], "%02x", addr->ebyte[5]);
dprintf("%s\n", buf);
}
status_t
wb_create_semaphores(wb_device* device)
{
device->rxSem = create_sem(0, "wb840 receive");
if (device->rxSem < B_OK) {
LOG(("Couldn't create sem, sem_id %ld\n", device->rxSem));
return device->rxSem;
}
device->txSem = create_sem(WB_TX_LIST_CNT, "wb840 transmit");
if (device->txSem < B_OK) {
LOG(("Couldn't create sem, sem_id %ld\n", device->txSem));
delete_sem(device->rxSem);
return device->txSem;
}
set_sem_owner(device->rxSem, B_SYSTEM_TEAM);
set_sem_owner(device->txSem, B_SYSTEM_TEAM);
device->rxLock = 0;
device->txLock = 0;
return B_OK;
}
void
wb_delete_semaphores(wb_device* device)
{
if (device->rxSem >= 0)
delete_sem(device->rxSem);
if (device->txSem >= 0)
delete_sem(device->txSem);
}
status_t
wb_create_rings(wb_device* device)
{
int i;
device->rxArea = create_area("wb840 rx buffer",
(void**)&device->rxBuffer[0], B_ANY_KERNEL_ADDRESS,
ROUND_TO_PAGE_SIZE(WB_BUFBYTES * WB_RX_LIST_CNT),
B_32_BIT_FULL_LOCK, B_READ_AREA | B_WRITE_AREA);
if (device->rxArea < B_OK)
return device->rxArea;
for (i = 1; i < WB_RX_LIST_CNT; i++) {
device->rxBuffer[i] = (void*)(((addr_t)device->rxBuffer[0])
+ (i * WB_BUFBYTES));
}
for (i = 0; i < WB_RX_LIST_CNT; i++) {
device->rxDescriptor[i].wb_status = 0;
device->rxDescriptor[i].wb_ctl = WB_RXCTL_RLINK;
wb_put_rx_descriptor(&device->rxDescriptor[i]);
device->rxDescriptor[i].wb_data = physicalAddress(
device->rxBuffer[i], WB_BUFBYTES);
device->rxDescriptor[i].wb_next = physicalAddress(
&device->rxDescriptor[(i + 1) & WB_RX_CNT_MASK],
sizeof(struct wb_desc));
}
device->rxFree = WB_RX_LIST_CNT;
device->txArea = create_area("wb840 tx buffer",
(void**)&device->txBuffer[0], B_ANY_KERNEL_ADDRESS,
ROUND_TO_PAGE_SIZE(WB_BUFBYTES * WB_TX_LIST_CNT),
B_32_BIT_FULL_LOCK, B_READ_AREA | B_WRITE_AREA);
if (device->txArea < B_OK) {
delete_area(device->rxArea);
return device->txArea;
}
for (i = 1; i < WB_TX_LIST_CNT; i++) {
device->txBuffer[i] = (void*)(((addr_t)device->txBuffer[0])
+ (i * WB_BUFBYTES));
}
for (i = 0; i < WB_TX_LIST_CNT; i++) {
device->txDescriptor[i].wb_status = 0;
device->txDescriptor[i].wb_ctl = WB_TXCTL_TLINK;
device->txDescriptor[i].wb_data = physicalAddress(
device->txBuffer[i], WB_BUFBYTES);
device->txDescriptor[i].wb_next = physicalAddress(
&device->txDescriptor[(i + 1) & WB_TX_CNT_MASK],
sizeof(struct wb_desc));
}
if (wb_stop(device) == B_OK) {
write32(device->reg_base + WB_RXADDR,
physicalAddress(&device->rxDescriptor[0], sizeof(struct wb_desc)));
write32(device->reg_base + WB_TXADDR,
physicalAddress(&device->txDescriptor[0], sizeof(struct wb_desc)));
}
return B_OK;
}
void
wb_delete_rings(wb_device* device)
{
delete_area(device->rxArea);
delete_area(device->txArea);
}
int32
wb_read_mode(wb_device* info)
{
uint16 autoAdv;
uint16 autoLinkPartner;
int32 speed;
int32 duplex;
uint16 status = mii_readstatus(info);
if (!(status & MII_STATUS_LINK)) {
LOG((DEVICE_NAME ": no link detected (status = %x)\n", status));
return 0;
}
autoAdv = wb_miibus_readreg(info, info->phy, MII_AUTONEG_ADV);
autoLinkPartner = wb_miibus_readreg(info, info->phy,
MII_AUTONEG_LINK_PARTNER);
status = autoAdv & autoLinkPartner;
speed = status & (MII_NWAY_TX | MII_NWAY_TX_FDX)
? LINK_SPEED_100_MBIT : LINK_SPEED_10_MBIT;
duplex = status & (MII_NWAY_TX_FDX | MII_NWAY_T_FDX)
? LINK_FULL_DUPLEX : LINK_HALF_DUPLEX;
info->autoNegotiationComplete = true;
LOG((DEVICE_NAME ": linked, 10%s MBit, %s duplex\n",
speed == LINK_SPEED_100_MBIT ? "0" : "",
duplex == LINK_FULL_DUPLEX ? "full" : "half"));
return speed | duplex;
}
void
wb_set_mode(wb_device* info, int mode)
{
uint32 cfgAddress = (uint32)info->reg_base + WB_NETCFG;
uint32 configFlags = 0;
status_t status;
info->speed = mode & LINK_SPEED_MASK;
info->full_duplex = mode & LINK_DUPLEX_MASK;
status = wb_stop(info);
if ((mode & LINK_DUPLEX_MASK) == LINK_FULL_DUPLEX)
configFlags |= WB_NETCFG_FULLDUPLEX;
if (info->speed == LINK_SPEED_100_MBIT)
configFlags |= WB_NETCFG_100MBPS;
write32(cfgAddress, configFlags);
if (status == B_OK)
WB_SETBIT(cfgAddress, WB_NETCFG_TX_ON|WB_NETCFG_RX_ON);
}