root/src/add-ons/kernel/drivers/network/ether/wb840/wb840.c
/* Copyright (c) 2003-2011
 * 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))

// MII chip info table
#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;

        // status bit has to be retrieved 2 times
        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;
}


// Prepares a RX descriptor to be used by the chip
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;

        // ToDo: need to be changed, select PHY in relation to the link mode
        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;
        // search for total of 32 possible MII PHY addresses
        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)
                        // this MII is not accessable
                        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);

        // Disable early TX/RX interrupt, as we can't take advantage
        // from them, at least for now.
        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!!!"));

        /* Wait a bit while the chip reorders his toughts */
        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);

                // Check if link lost
                if ((status & MII_STATUS_LINK) == 0)
                        device->link = false;
        } else {
                uint16 status;
                wb_selectPHY(device);

                // Check if we have a new link
                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)
{
        // TODO: Basically we just config the filter to accept broadcasts
        // packets. We'll need also to configure it to multicast.
        WB_SETBIT(device->reg_base + WB_NETCFG, WB_NETCFG_RX_BROAD);
}


/***************** Interrupt handling ******************************/
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--;
        }

        // Re-enable receive queue
        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++; // this many buffers are free
                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;

        // TODO: Handle other interrupts

        acquire_spinlock(&device->intLock);

        status = read32(device->reg_base + WB_ISR);

        // Did this card request the interrupt ?
        if (status & WB_INTRS) {
                // Clean all the interrupts bits
                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"));
                        // Something is screwed
                }

                if (status & WB_ISR_RX_ERR) {
                        LOG(("WB_ISR_RX_ERR\n"));
                        // TODO: Do something useful
                }

                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"));
                        // TODO: Jack up TX Threshold
                }

                if (status & WB_ISR_TX_IDLE) {
                        LOG(("WB_ISR_TX_IDLE\n"));
                }

                if (status & WB_ISR_TX_OK) {
                        LOG(("WB_ISR_TX_OK\n"));
                        // So what ?
                }

                if (status & WB_ISR_BUS_ERR) {
                        LOG(("WB_ISR_BUS_ERROR: %lx\n", (status & WB_ISR_BUSERRTYPE) >> 4));
                        //wb_reset(device);
                }

                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;
        }

        // auto negotiation completed
        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);
}