+// vim: set et sw=4 sts=4 :
+
+#include "eth.h"
+#include "dp8390.h"
+
+#ifdef DEBUG
+void sleep(unsigned char);
+#ifdef SDCC
+static xdata at 0x0080 byte leds0;
+static xdata at 0x00c0 byte leds1;
+#else
+static byte xdata leds0 _at_ 0x0080;
+static byte xdata leds1 _at_ 0x00c0;
+#endif
+#endif
+
+/// Datos persistentes del módulo
+static union
+{
+ byte send_len; ///> Tamaño del frame que será enviado
+ byte next_pkt; ///> Próximo frame a obtener
+}
+persistent;
+
+/// Cambia de página sin modificar los demás bits del CR
+#define SELECT_REG_PAGE(page) \
+ do \
+ { \
+ write_reg(CR, read_reg(CR) & ~(PS1 | PS0)); \
+ write_reg(CR, read_reg(CR) | (page << 6)); \
+ } \
+ while (0)
+
+/// Aborta (o completa) el DMA limpiando el ISR
+#define ABORT_DMA(flags) \
+ do \
+ { \
+ write_reg(CR, flags); \
+ write_reg(ISR, RDC); \
+ } \
+ while (0)
+
+
+static void write_reg(unsigned char reg, unsigned char wr_data)
+{
+ // Select register address.
+ ADDR_PORT &= ~ADDR_PORT_MASK;
+ ADDR_PORT |= reg;
+
+ // Output register data to port.
+ DATA_PORT = wr_data;
+
+ // Clock register data into RTL8019AS.
+ // IOR & IOW are both active low.
+ NICE = 0;
+ IOW = 0;
+ IOW = 1;
+ NICE = 1;
+
+ // Set register data port as input again.
+ DATA_PORT = DATA_PORT_MASK;
+}
+
+
+static unsigned char read_reg(unsigned char reg)
+{
+ // Select register address.
+ ADDR_PORT &= ~ADDR_PORT_MASK;
+ ADDR_PORT |= reg;
+
+ // Enable register data output from RTL8019AS.
+ NICE = 0;
+ IOR = 0;
+
+ // Read register data from port.
+ reg = DATA_PORT;
+
+ // Disable register data output from RTL8019AS.
+ IOR = 1;
+ NICE = 1;
+
+ return reg;
+}
+
+/** Resetea placa de red en caso de buffer overflow */
+static void reset()
+{
+ bit retransmit = read_reg(CR) & TXP;
+
+ // If the receive buffer ring has overflowed we dump the whole
+ // thing and start over. There is no way of knowing whether the
+ // data it contains is uncorrupted, or will cause us grief.
+
+ // Stop RTL8019AS and abort DMA operation.
+ write_reg(CR, STOP);
+
+ // Wait for controller to halt after any current tx completes.
+ while(!(read_reg(ISR) & RST)) continue;
+
+ // Reset remote byte count registers.
+ write_reg(RBCR0, 0x00);
+ write_reg(RBCR1, 0x00);
+
+ // Check whether currently transmitting a packet.
+ if(retransmit)
+ {
+ // If neither a successful transmission nor a tx abort error
+ // has occured, then flag current tx packet for resend.
+ if(read_reg(ISR) & (PTX | TXE))
+ {
+ retransmit = 0;
+ }
+ }
+
+ // Set transmit configuration register to loopback internally.
+ write_reg(TCR, MODE1);
+
+ // Restart the RTL8019AS.
+ write_reg(CR, START);
+
+ // Re-initialise last receive buffer read pointer.
+ write_reg(BNRY, RX_PAGE_START);
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(1);
+
+ // Re-initialise current packet receive buffer page pointer.
+ write_reg(CURR, RX_PAGE_START + 1);
+
+ // Select RTL8019AS register page 0.
+ SELECT_REG_PAGE(0);
+
+ // Clear rx buffer overflow & packet received interrupt flags.
+ write_reg(ISR, PRX | OVW);
+
+ // Re-itialise transmit configuration reg for normal operation.
+ write_reg(TCR, MODE0);
+
+ if(retransmit)
+ {
+ // Retransmit packet in RTL8019AS local tx buffer.
+ write_reg(CR, START | TXP);
+ }
+}
+
+
+/** Inicializa dispositivo de red
+ * @return true si se inicializó correctamente, false si no
+ */
+bool netdev_init()
+{
+ // Set IOR & IOW as they're active low.
+ IOR = 1;
+ IOW = 1;
+ NICE = 1;
+
+ // Set register data port as input.
+ DATA_PORT = DATA_PORT_MASK;
+
+ // Configure RTL8019AS ethernet controller.
+
+ // Keil startup code takes 4ms to execute (18.432MHz, X1 mode).
+ // That leaves plenty of time for the RTL8019AS to read it's
+ // configuration in from the 9346 EEPROM before we get here.
+
+ // Select RTL8019AS register page 0.
+ SELECT_REG_PAGE(0);
+
+ // Check if RTL8019AS fully reset.
+ if(!(read_reg(ISR) & RST))
+ {
+ return 0;
+ }
+
+ // Stop RTL8019AS, select page 0 and abort DMA operation.
+ write_reg(CR, STOP);
+
+ // Initialise data configuration register.
+ // FIFO threshold 8 bytes, no loopback, don't use auto send packet.
+ write_reg(DCR, FT1 | LS);
+
+ // Reset remote byte count registers.
+ write_reg(RBCR0, 0u);
+ write_reg(RBCR1, 0u);
+
+ // Receive configuration register to monitor mode.
+ write_reg(RCR, MON);
+
+ // Initialise transmit configuration register to loopback internally.
+ write_reg(TCR, MODE1);
+
+ // Clear interrupt status register bits by writing 1 to each.
+ write_reg(ISR, ALL);
+
+ // Mask all interrupts in mask register.
+ write_reg(IMR, NONE);
+
+ // Obtengo MAC de la placa
+ write_reg(RBCR0, 12u); // Vamos a leer 12 bytes (2 x 6)
+ write_reg(RBCR1, 0u);
+ write_reg(RSAR0, 0u); // En la dirección 0x0000
+ write_reg(RSAR1, 0u);
+ write_reg(CR, READ); // Comienza lectura
+ eth_addr_local[0] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+ eth_addr_local[1] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+ eth_addr_local[2] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+ eth_addr_local[3] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+ eth_addr_local[4] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+ eth_addr_local[5] = read_reg(RDMA);
+ read_reg(RDMA); // Ignoramos porque viene como un word
+
+ // Abort/ complete DMA operation.
+ ABORT_DMA(STOP);
+
+ // Set transmit page start.
+ write_reg(TPSR, TX_PAGE_START);
+
+ // Set receive buffer page start.
+ write_reg(PSTART, RX_PAGE_START);
+
+ // Initialise last receive buffer read pointer.
+ write_reg(BNRY, RX_PAGE_START);
+
+ // Set receive buffer page stop.
+ write_reg(PSTOP, RX_PAGE_STOP);
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(1);
+
+ // Initialise current packet receive buffer page pointer
+ write_reg(CURR, RX_PAGE_START + 1);
+
+ // Set physical address
+ write_reg(PAR0, eth_addr_local[0]);
+ write_reg(PAR1, eth_addr_local[1]);
+ write_reg(PAR2, eth_addr_local[2]);
+ write_reg(PAR3, eth_addr_local[3]);
+ write_reg(PAR4, eth_addr_local[4]);
+ write_reg(PAR5, eth_addr_local[5]);
+
+ // Restart RTL8019AS.
+ write_reg(CR, START);
+
+ // Initialise transmit configuration register for normal operation.
+ write_reg(TCR, MODE0);
+
+ // Receive configuration register to accept broadcast packets.
+ write_reg(RCR, AB);
+
+ return 1;
+}
+
+
+/** Comienza el envío de un nuevo frame
+ * @param len Tamaño del frame a enviar
+ */
+void netdev_send_start()
+{
+ persistent.send_len = 0;
+ // Wait until pending transmit operation completes.
+ while(read_reg(CR) & TXP) continue;
+
+ // Set remote DMA start address registers to indicate where to load packet.
+ write_reg(RSAR0, 0u);
+ write_reg(RSAR1, TX_PAGE_START);
+
+ // Set remote DMA byte count registers to indicate length of packet load.
+ write_reg(RBCR0, MAX_PACKET_LEN); // Tamaño máximo en principio
+ write_reg(RBCR1, 0u);
+
+ // Initiate DMA transfer of uip_buf & uip_appdata buffers to RTL8019AS.
+ write_reg(CR, WRITE | STA);
+}
+
+/** Escribe un byte al buffer de la placa de red para ser enviado
+ * @precond netdev_send_start() debe haber sido ejecutada
+ * @param b Byte a enviar
+ */
+void netdev_send_byte(byte b)
+{
+ persistent.send_len++;
+ write_reg(RDMA, b);
+}
+
+/** Escribe un word al buffer de la placa de red para ser enviado
+ * @precond netdev_send_start() debe haber sido ejecutada
+ * @param w Word a enviar
+ */
+void netdev_send_word(uint16 w)
+{
+ persistent.send_len += 2;
+ write_reg(RDMA, HIGH(w));
+ write_reg(RDMA, LOW(w));
+}
+
+/** Finaliza el envío del frame
+ * @precond netdev_send_start() debe haber sido ejecutada
+ */
+void netdev_send_end()
+{
+ // Abort/ complete DMA operation.
+ ABORT_DMA(START);
+
+ // Set transmit page start to indicate packet start.
+ write_reg(TPSR, TX_PAGE_START);
+
+ // Ethernet packets must be > 60 bytes, otherwise are rejected as runts.
+ if (persistent.send_len < MIN_PACKET_LEN)
+ {
+ persistent.send_len = MIN_PACKET_LEN;
+ }
+
+ // Set transmit byte count registers to indicate packet length.
+ write_reg(TBCR0, LOW(persistent.send_len));
+ write_reg(TBCR1, 0u);
+
+ // Issue command for RTL8019AS to transmit packet from it's local buffer.
+ write_reg(CR, START | TXP);
+}
+
+/** Comienza la lectura de un nuevo frame
+ * @return Cantidad de bytes del frame leído
+ */
+byte netdev_recv_start()
+{
+ // Check if the rx buffer has overflowed.
+ if (read_reg(ISR) & OVW)
+ {
+ byte current;
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(1);
+
+ // Retrieve current receive buffer page
+ current = read_reg(CURR);
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(0);
+
+ if (read_reg(BNRY) == current)
+ {
+#ifdef DEBUG
+ leds1 = ~0x01;
+ leds2 = ~read_reg(ISR);
+ sleep(5);
+ leds1 = ~0x02;
+ leds2 = ~read_reg(BNRY);
+ sleep(5);
+ leds1 = ~0x04;
+ leds2 = ~current;
+ sleep(5);
+#endif
+ reset();
+ }
+ return 0u;
+ }
+ // Check if there is a packet in the rx buffer.
+ else if (read_reg(ISR) & PRX)
+ {
+ struct buf_hdr_t
+ {
+ byte status; // Estado del frame recibido
+ byte next; // Offset del próximo frame
+ uint16 len; // Tamaño del frame
+ }
+ buf_hdr;
+ byte current;
+ byte bnry;
+
+ // Retrieve packet header. (status, next_ptr, length_l, length_h)
+
+ // Set remote DMA start address registers to packet header.
+ bnry = read_reg(BNRY) + 1;
+ if (bnry >= RX_PAGE_STOP)
+ bnry = RX_PAGE_START;
+ write_reg(RSAR0, 0u);
+ write_reg(RSAR1, bnry);
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(1);
+
+ // Retrieve current receive buffer page
+ current = read_reg(CURR);
+
+ // Select RTL8019AS register page 1.
+ SELECT_REG_PAGE(0);
+
+ // Check if last packet has been removed from rx buffer.
+ if(bnry == current)
+ {
+ // Clear packet received interrupt flag.
+ write_reg(ISR, PRX | RXE);
+ return 0u;
+ }
+
+ // Set remote DMA byte count registers to packet header length.
+ write_reg(RBCR0, sizeof(struct buf_hdr_t));
+ write_reg(RBCR1, 0u);
+
+ // Clear remote DMA complete interrupt status register bit.
+ write_reg(ISR, RDC);
+
+ // Initiate DMA transfer of packet header.
+ write_reg(CR, READ);
+
+ // Packet status.
+ buf_hdr.status = read_reg(RDMA);
+
+ // Save next packet pointer.
+ buf_hdr.next = persistent.next_pkt = read_reg(RDMA);
+
+ // Retrieve packet data length and subtract CRC bytes.
+ buf_hdr.len = read_reg(RDMA) - sizeof(struct buf_hdr_t);
+
+ // Si es muy grande, muy chico o hubo error, lo descartamos
+ if ((buf_hdr.len < MIN_PACKET_LEN) || (buf_hdr.len > MAX_PACKET_LEN)
+ || ((buf_hdr.status & 0x0F) != RXSOK)
+ || read_reg(RDMA)) // Parte alta del tamaño
+ {
+ ABORT_DMA(START); // Termina DMA
+ write_reg(BNRY, buf_hdr.next - 1); // Pasa al próximo frame
+ return 0;
+ }
+
+ // Abort/ complete DMA operation.
+ ABORT_DMA(START);
+
+ // Set remote DMA start address registers to packet data.
+ write_reg(RSAR0, sizeof(struct buf_hdr_t));
+ write_reg(RSAR1, bnry);
+
+ // Set remote DMA byte count registers to packet data length.
+ write_reg(RBCR0, buf_hdr.len);
+ write_reg(RBCR1, 0u);
+
+ // Initiate DMA transfer of packet data.
+ write_reg(CR, READ);
+
+ return buf_hdr.len;
+ }
+ return 0;
+}
+
+/** Lee un byte del buffer de la placa de red
+ * @precond netdev_recv_start() debe haber sido ejecutada
+ */
+byte netdev_recv_byte()
+{
+ return read_reg(RDMA);
+}
+
+/** Lee un word del buffer de la placa de red
+ * @precond netdev_recv_start() debe haber sido ejecutada
+ */
+uint16 netdev_recv_word()
+{
+ uint16 w = netdev_recv_byte() << 8;
+ return w + netdev_recv_byte();
+}
+
+/** Finaliza la lectura del frame
+ * @precond netdev_recv_start() debe haber sido ejecutada
+ */
+void netdev_recv_end()
+{
+ // Abort/ complete DMA operation.
+ ABORT_DMA(START);
+
+ // Advance boundary pointer to next packet start.
+ write_reg(BNRY, persistent.next_pkt - 1);
+}
+