/*
    ChibiOS/RT - Copyright (C) 2006-2007 Giovanni Di Sirio.
    This file is part of ChibiOS/RT.
    ChibiOS/RT 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 3 of the License, or
    (at your option) any later version.
    ChibiOS/RT 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, see .
*/
/**
 * @file AT91SAM7X/sam7x_emac.c
 * @brief AT91SAM7X EMAC driver code.
 * @addtogroup AT91SAM7X_EMAC
 * @{
 */
#include 
#include 
#include "board.h"
#include "sam7x_emac.h"
#include "mii.h"
#include "at91lib/aic.h"
EventSource EMACFrameTransmitted;       /* A frame was transmitted.     */
EventSource EMACFrameReceived;          /* A frame was received.        */
#ifndef __DOXYGEN__
//static int received;                    /* Buffered frames counter.     */
static bool_t link_up;                  /* Last from EMACGetLinkStatus()*/
static uint8_t default_mac[] = {0xAA, 0x55, 0x13, 0x37, 0x01, 0x10};
static BufDescriptorEntry rent[EMAC_RECEIVE_BUFFERS] __attribute__((aligned(8)));
static uint8_t rbuffers[EMAC_RECEIVE_BUFFERS * EMAC_RECEIVE_BUFFERS_SIZE] __attribute__((aligned(8)));
static BufDescriptorEntry *rxptr;
static BufDescriptorEntry tent[EMAC_TRANSMIT_BUFFERS] __attribute__((aligned(8)));
static uint8_t tbuffers[EMAC_TRANSMIT_BUFFERS * EMAC_TRANSMIT_BUFFERS_SIZE] __attribute__((aligned(8)));
static BufDescriptorEntry *txptr;
#endif
#define AT91C_PB15_ERXDV AT91C_PB15_ERXDV_ECRSDV
#define EMAC_PIN_MASK (AT91C_PB0_ETXCK_EREFCK | \
                       AT91C_PB1_ETXEN  | AT91C_PB2_ETX0   | \
                       AT91C_PB3_ETX1   | AT91C_PB4_ECRS   | \
                       AT91C_PB5_ERX0   | AT91C_PB6_ERX1   | \
                       AT91C_PB7_ERXER  | AT91C_PB8_EMDC   | \
                       AT91C_PB9_EMDIO  | AT91C_PB10_ETX2  | \
                       AT91C_PB11_ETX3  | AT91C_PB12_ETXER | \
                       AT91C_PB13_ERX2  | AT91C_PB14_ERX3  | \
                       AT91C_PB15_ERXDV | AT91C_PB16_ECOL  | \
                       AT91C_PB17_ERXCK)
#define PHY_LATCHED_PINS (AT91C_PB4_ECRS   | AT91C_PB5_ERX0  | AT91C_PB6_ERX1  | \
                          AT91C_PB7_ERXER  | AT91C_PB13_ERX2 | AT91C_PB14_ERX3 | \
                          AT91C_PB15_ERXDV | AT91C_PB16_ECOL | PIOB_PHY_IRQ_MASK)
/*
 * PHY utilities.
 */
static uint32_t phy_get(uint8_t regno) {
  AT91C_BASE_EMAC->EMAC_MAN = (1 << 30) |               // SOF = 01
                              (2 << 28) |               // RW = 10
                              (PHY_ADDRESS << 23) |
                              (regno << 18) |
                              (2 << 16);                // CODE = 10
  while (!( AT91C_BASE_EMAC->EMAC_NSR & AT91C_EMAC_IDLE))
    ;
  return AT91C_BASE_EMAC->EMAC_MAN & 0xFFFF;
}
/*static void phy_put(uint8_t regno, uint32_t val) {
  AT91C_BASE_EMAC->EMAC_MAN = (1 << 30) |               // SOF = 01
                              (1 << 28) |               // RW = 01
                              (PHY_ADDRESS << 23) |
                              (regno << 18) |
                              (2 << 16) |               // CODE = 10
                              val;
  while (!( AT91C_BASE_EMAC->EMAC_NSR & AT91C_EMAC_IDLE))
    ;
}*/
#define RSR_BITS (AT91C_EMAC_BNA | AT91C_EMAC_REC | AT91C_EMAC_OVR)
#define TSR_BITS (AT91C_EMAC_UBR | AT91C_EMAC_COL | AT91C_EMAC_RLES | \
                  AT91C_EMAC_BEX | AT91C_EMAC_COMP | AT91C_EMAC_UND)
__attribute__((noinline))
static void ServeInterrupt(void) {
  uint32_t isr, rsr, tsr;
  /* Fix for the EMAC errata */
  isr = AT91C_BASE_EMAC->EMAC_ISR;
  rsr = AT91C_BASE_EMAC->EMAC_RSR;
  tsr = AT91C_BASE_EMAC->EMAC_TSR;
  if ((isr & AT91C_EMAC_RCOMP) || (rsr & RSR_BITS)) {
    if (rsr & AT91C_EMAC_REC) {
//      received++;
      chSysLockFromIsr();
      chEvtBroadcastI(&EMACFrameReceived);
      chSysUnlockFromIsr();
    }
    AT91C_BASE_EMAC->EMAC_RSR = RSR_BITS;
  }
  if ((isr & AT91C_EMAC_TCOMP) || (tsr & TSR_BITS)) {
    if (tsr & AT91C_EMAC_COMP) {
      chSysLockFromIsr();
      chEvtBroadcastI(&EMACFrameTransmitted);
      chSysUnlockFromIsr();
    }
    AT91C_BASE_EMAC->EMAC_TSR = TSR_BITS;
  }
  AT91C_BASE_AIC->AIC_EOICR = 0;
}
CH_IRQ_HANDLER(EMACIrqHandler) {
  CH_IRQ_PROLOGUE();
  ServeInterrupt();
  CH_IRQ_EPILOGUE();
}
/*
 * EMAC subsystem initialization.
 */
void emac_init(int prio) {
  int i;
  /*
   * Buffers initialization.
   */
//  received = 0;
  for (i = 0; i < EMAC_RECEIVE_BUFFERS; i++) {
    rent[i].w1 = (uint32_t)&rbuffers[i * EMAC_RECEIVE_BUFFERS_SIZE];
    rent[i].w2 = 0;
  }
  rent[EMAC_RECEIVE_BUFFERS - 1].w1 |= W1_R_WRAP;
  rxptr = rent;
  for (i = 0; i < EMAC_TRANSMIT_BUFFERS; i++) {
    tent[i].w1 = (uint32_t)&tbuffers[i * EMAC_TRANSMIT_BUFFERS_SIZE];
    tent[i].w2 = EMAC_TRANSMIT_BUFFERS_SIZE | W2_T_USED;
  }
  tent[EMAC_TRANSMIT_BUFFERS - 1].w2 |= W2_T_WRAP;
  txptr = tent;
  /*
   * Disables the pullups on all the pins that are latched on reset by the PHY.
   * The status latched into the PHY is:
   *   PHYADDR  = 00001
   *   PCS_LPBK = 0 (disabled)
   *   ISOLATE  = 0 (disabled)
   *   RMIISEL  = 0 (MII mode)
   *   RMIIBTB  = 0 (BTB mode disabled)
   *   SPEED    = 1 (100mbps)
   *   DUPLEX   = 1 (full duplex)
   *   ANEG_EN  = 1 (auto negotiation enabled)
   */
  AT91C_BASE_PIOB->PIO_PPUDR = PHY_LATCHED_PINS;
  /*
   * PHY power control.
   */
  AT91C_BASE_PIOB->PIO_OER = PIOB_PHY_PD_MASK;       // Becomes an output.
  AT91C_BASE_PIOB->PIO_PPUDR = PIOB_PHY_PD_MASK;     // Default pullup disabled.
  AT91C_BASE_PIOB->PIO_SODR = PIOB_PHY_PD_MASK;      // Output to high level.
  /*
   * PHY reset by pulsing the NRST pin.
   */
  AT91C_BASE_RSTC->RSTC_RMR = 0xA5000100;
  AT91C_BASE_RSTC->RSTC_RCR = 0xA5000000 | AT91C_RSTC_EXTRST;
  while (!(AT91C_BASE_RSTC->RSTC_RSR & AT91C_RSTC_NRSTL))
    ;
  /*
   * EMAC pins setup and clock enable. Note, PB18 is not included because it is
   * used as #PD control and not as EF100.
   */
  AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_EMAC;
  AT91C_BASE_PIOB->PIO_ASR = EMAC_PIN_MASK;
  AT91C_BASE_PIOB->PIO_PDR = EMAC_PIN_MASK;
  AT91C_BASE_PIOB->PIO_PPUDR = EMAC_PIN_MASK;
  /*
   * EMAC setup.
   */
  AT91C_BASE_EMAC->EMAC_NCR = 0;                        // Initial setting.
  AT91C_BASE_EMAC->EMAC_NCFGR = 2 << 10;                // MDC-CLK = MCK / 32
  AT91C_BASE_EMAC->EMAC_USRIO = AT91C_EMAC_CLKEN;       // Enable EMAC in MII mode
  AT91C_BASE_EMAC->EMAC_RBQP = (AT91_REG)rent;          // RX buffers list
  AT91C_BASE_EMAC->EMAC_TBQP = (AT91_REG)tent;          // TX buffers list
  AT91C_BASE_EMAC->EMAC_RSR = AT91C_EMAC_OVR |
                              AT91C_EMAC_REC |
                              AT91C_EMAC_BNA;           // Clears RSR
  AT91C_BASE_EMAC->EMAC_NCFGR |= AT91C_EMAC_DRFCS;      // Initial NCFGR settings
  AT91C_BASE_EMAC->EMAC_NCR |= AT91C_EMAC_TE |
                               AT91C_EMAC_RE |
                               AT91C_EMAC_CLRSTAT;      // Initial NCR settings
  EMACSetAddress(default_mac);
  /*
   * PHY detection and settings.
   */
  AT91C_BASE_EMAC->EMAC_NCR |= AT91C_EMAC_MPE;
  if ((phy_get(MII_PHYSID1) != (MII_MICREL_ID >> 16)) ||
      ((phy_get(MII_PHYSID2) & 0xFFF0) != (MII_MICREL_ID & 0xFFF0)))
    chSysHalt();
  /*
   * Waits for auto-negotiation to end and then detects the link status.
   */
  AT91C_BASE_EMAC->EMAC_NCR &= ~AT91C_EMAC_MPE;
  /*
   * Interrupt setup.
   */
  AT91C_BASE_EMAC->EMAC_IER = AT91C_EMAC_RCOMP | AT91C_EMAC_TCOMP;
  AIC_ConfigureIT(AT91C_ID_EMAC,
                  AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL | prio,
                  EMACIrqHandler);
  AIC_EnableIT(AT91C_ID_EMAC);
  /*
   * Event sources setup.
   */
  chEvtInit(&EMACFrameTransmitted);
  chEvtInit(&EMACFrameReceived);
}
/*
 * Set the MAC address.
 */
void EMACSetAddress(const uint8_t *eaddr) {
  AT91C_BASE_EMAC->EMAC_SA1L = (AT91_REG)((eaddr[3] << 24) | (eaddr[2] << 16) |
                                          (eaddr[1] << 8) | eaddr[0]);
  AT91C_BASE_EMAC->EMAC_SA1H = (AT91_REG)((eaddr[5] << 8) | eaddr[4]);
}
/*
 * Returns TRUE if the link is active. To be invoked at regular intervals in
 * order to monitor the link.
 * @note It is not thread-safe.
 */
bool_t EMACGetLinkStatus(void) {
  uint32_t ncfgr, bmsr, bmcr, lpa;
  AT91C_BASE_EMAC->EMAC_NCR |= AT91C_EMAC_MPE;
  (void)phy_get(MII_BMSR);
  bmsr = phy_get(MII_BMSR);
  if (!(bmsr & BMSR_LSTATUS)) {
    AT91C_BASE_EMAC->EMAC_NCR &= ~AT91C_EMAC_MPE;
    return link_up = FALSE;
  }
  ncfgr = AT91C_BASE_EMAC->EMAC_NCFGR & ~(AT91C_EMAC_SPD | AT91C_EMAC_FD);
  bmcr = phy_get(MII_BMCR);
  if (bmcr & BMCR_ANENABLE) {
    lpa = phy_get(MII_LPA);
    if (lpa & (LPA_100HALF | LPA_100FULL | LPA_100BASE4))
      ncfgr |= AT91C_EMAC_SPD;
    if (lpa & (LPA_10FULL | LPA_100FULL))
      ncfgr |= AT91C_EMAC_FD;
  }
  else {
    if (bmcr & BMCR_SPEED100)
      ncfgr |= AT91C_EMAC_SPD;
    if (bmcr & BMCR_FULLDPLX)
      ncfgr |= AT91C_EMAC_FD;
  }
  AT91C_BASE_EMAC->EMAC_NCFGR = ncfgr;
  AT91C_BASE_EMAC->EMAC_NCR &= ~AT91C_EMAC_MPE;
  return link_up = TRUE;
}
/*
 * Allocates and locks a buffer for a transmission operation.
 */
BufDescriptorEntry *EMACGetTransmitBuffer(void) {
  BufDescriptorEntry *cptr;
  if (!link_up)
    return NULL;
  chSysLock();
  cptr = txptr;
  if (!(cptr->w2 & W2_T_USED) ||
       (cptr->w2 & W2_T_LOCKED)) {
    chSysUnlock();
    return FALSE;
  }
  cptr->w2 |= W2_T_LOCKED;        /* Locks the buffer while copying.*/
  if (++txptr >= &tent[EMAC_TRANSMIT_BUFFERS])
    txptr = tent;
  chSysUnlock();
  return cptr;
}
/*
 * Transmits a previously allocated buffer and then releases it.
 */
void EMACTransmit(BufDescriptorEntry *cptr, size_t size) {
  chDbgAssert(size <= EMAC_TRANSMIT_BUFFERS_SIZE,
              "EMACTransmit(), #1",
              "unexpected size");
  chSysLock();
  if (cptr < &tent[EMAC_TRANSMIT_BUFFERS - 1])
    cptr->w2 = size | W2_T_LAST_BUFFER;
  else
    cptr->w2 = size | W2_T_LAST_BUFFER | W2_T_WRAP;
  AT91C_BASE_EMAC->EMAC_NCR |= AT91C_EMAC_TSTART;
  chSysUnlock();
}
/*
 * Reads a buffered frame.
 * Returns TRUE if a frame was present and read else FALSE.
 * @note It is not thread-safe.
 */
bool_t EMACReceive(uint8_t *buf, size_t *sizep) {
  unsigned n;
  size_t size;
  uint8_t *p;
  bool_t overflow, found;
//  chSysLock();
//  if (received <= 0) {
//    chSysUnlock();
//    return FALSE;
//  }
//  received--;
//  chSysUnlock();
  n = EMAC_RECEIVE_BUFFERS;
  /*
   * Skips unused buffers, if any.
   */
skip:
  while (n && !(rxptr->w1 & W1_R_OWNERSHIP)) {
    if (++rxptr >= &rent[EMAC_RECEIVE_BUFFERS])
      rxptr = rent;
    n--;
  }
  /*
   * Skips fragments, if any.
   */
  while (n && (rxptr->w1 & W1_R_OWNERSHIP) && !(rxptr->w2 & W2_R_FRAME_START)) {
    rxptr->w1 &= ~W1_R_OWNERSHIP;
    if (++rxptr >= &rent[EMAC_RECEIVE_BUFFERS])
      rxptr = rent;
    n--;
  }
restart:
  p = buf;
  size = 0;
  found = overflow = FALSE;
  while (n && !found) {
    size_t segsize;
    if (!(rxptr->w1 & W1_R_OWNERSHIP))
      goto skip;        /* Empty buffer for some reason... */
    if (size && (rxptr->w2 & W2_R_FRAME_START))
      goto restart;     /* Another start buffer for some reason... */
    if (rxptr->w2 & W2_R_FRAME_END) {
      segsize = (rxptr->w2 & W2_T_LENGTH_MASK) - size;
      if (((rxptr->w2 & W2_T_LENGTH_MASK) > *sizep) ||
          (segsize > EMAC_RECEIVE_BUFFERS_SIZE))
        overflow = TRUE;
      found = TRUE;
    }
    else {
      segsize = EMAC_RECEIVE_BUFFERS_SIZE;
      if (size + segsize > *sizep)
        overflow = TRUE;
    }
    if (!overflow) {
      chDbgAssert(segsize <= 128, "EMACReceive(), #1", "");
      memcpy(p, (void *)(rxptr->w1 & W1_R_ADDRESS_MASK), segsize);
      p += segsize;
      size += segsize;
    }
    rxptr->w1 &= ~W1_R_OWNERSHIP;
    if (++rxptr >= &rent[EMAC_RECEIVE_BUFFERS])
      rxptr = rent;
    n--;
  }
  *sizep = size;
  return found && !overflow;
}
/** @} */