| /* |
| * (C) Copyright 2010 |
| * Reinhard Meyer, EMK Elektronik, reinhard.meyer@emk-elektronik.de |
| * Martin Krause, Martin.Krause@tqs.de |
| * reworked original enc28j60.c |
| * |
| * 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 <common.h> |
| #include <net.h> |
| #include <spi.h> |
| #include <malloc.h> |
| #include <netdev.h> |
| #include <miiphy.h> |
| #include "enc28j60.h" |
| |
| /* |
| * IMPORTANT: spi_claim_bus() and spi_release_bus() |
| * are called at begin and end of each of the following functions: |
| * enc_miiphy_read(), enc_miiphy_write(), enc_write_hwaddr(), |
| * enc_init(), enc_recv(), enc_send(), enc_halt() |
| * ALL other functions assume that the bus has already been claimed! |
| * Since NetReceive() might call enc_send() in return, the bus must be |
| * released, NetReceive() called and claimed again. |
| */ |
| |
| /* |
| * Controller memory layout. |
| * We only allow 1 frame for transmission and reserve the rest |
| * for reception to handle as many broadcast packets as possible. |
| * Also use the memory from 0x0000 for receiver buffer. See errata pt. 5 |
| * 0x0000 - 0x19ff 6656 bytes receive buffer |
| * 0x1a00 - 0x1fff 1536 bytes transmit buffer = |
| * control(1)+frame(1518)+status(7)+reserve(10). |
| */ |
| #define ENC_RX_BUF_START 0x0000 |
| #define ENC_RX_BUF_END 0x19ff |
| #define ENC_TX_BUF_START 0x1a00 |
| #define ENC_TX_BUF_END 0x1fff |
| #define ENC_MAX_FRM_LEN 1518 |
| #define RX_RESET_COUNTER 1000 |
| |
| /* |
| * For non data transfer functions, like phy read/write, set hwaddr, init |
| * we do not need a full, time consuming init including link ready wait. |
| * This enum helps to bring the chip through the minimum necessary inits. |
| */ |
| enum enc_initstate {none=0, setupdone, linkready}; |
| typedef struct enc_device { |
| struct eth_device *dev; /* back pointer */ |
| struct spi_slave *slave; |
| int rx_reset_counter; |
| u16 next_pointer; |
| u8 bank; /* current bank in enc28j60 */ |
| enum enc_initstate initstate; |
| } enc_dev_t; |
| |
| /* |
| * enc_bset: set bits in a common register |
| * enc_bclr: clear bits in a common register |
| * |
| * making the reg parameter u8 will give a compile time warning if the |
| * functions are called with a register not accessible in all Banks |
| */ |
| static void enc_bset(enc_dev_t *enc, const u8 reg, const u8 data) |
| { |
| u8 dout[2]; |
| |
| dout[0] = CMD_BFS(reg); |
| dout[1] = data; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| } |
| |
| static void enc_bclr(enc_dev_t *enc, const u8 reg, const u8 data) |
| { |
| u8 dout[2]; |
| |
| dout[0] = CMD_BFC(reg); |
| dout[1] = data; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| } |
| |
| /* |
| * high byte of the register contains bank number: |
| * 0: no bank switch necessary |
| * 1: switch to bank 0 |
| * 2: switch to bank 1 |
| * 3: switch to bank 2 |
| * 4: switch to bank 3 |
| */ |
| static void enc_set_bank(enc_dev_t *enc, const u16 reg) |
| { |
| u8 newbank = reg >> 8; |
| |
| if (newbank == 0 || newbank == enc->bank) |
| return; |
| switch (newbank) { |
| case 1: |
| enc_bclr(enc, CTL_REG_ECON1, |
| ENC_ECON1_BSEL0 | ENC_ECON1_BSEL1); |
| break; |
| case 2: |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_BSEL0); |
| enc_bclr(enc, CTL_REG_ECON1, ENC_ECON1_BSEL1); |
| break; |
| case 3: |
| enc_bclr(enc, CTL_REG_ECON1, ENC_ECON1_BSEL0); |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_BSEL1); |
| break; |
| case 4: |
| enc_bset(enc, CTL_REG_ECON1, |
| ENC_ECON1_BSEL0 | ENC_ECON1_BSEL1); |
| break; |
| } |
| enc->bank = newbank; |
| } |
| |
| /* |
| * local functions to access SPI |
| * |
| * reg: register inside ENC28J60 |
| * data: 8/16 bits to write |
| * c: number of retries |
| * |
| * enc_r8: read 8 bits |
| * enc_r16: read 16 bits |
| * enc_w8: write 8 bits |
| * enc_w16: write 16 bits |
| * enc_w8_retry: write 8 bits, verify and retry |
| * enc_rbuf: read from ENC28J60 into buffer |
| * enc_wbuf: write from buffer into ENC28J60 |
| */ |
| |
| /* |
| * MAC and MII registers need a 3 byte SPI transfer to read, |
| * all other registers need a 2 byte SPI transfer. |
| */ |
| static int enc_reg2nbytes(const u16 reg) |
| { |
| /* check if MAC or MII register */ |
| return ((reg >= CTL_REG_MACON1 && reg <= CTL_REG_MIRDH) || |
| (reg >= CTL_REG_MAADR1 && reg <= CTL_REG_MAADR4) || |
| (reg == CTL_REG_MISTAT)) ? 3 : 2; |
| } |
| |
| /* |
| * Read a byte register |
| */ |
| static u8 enc_r8(enc_dev_t *enc, const u16 reg) |
| { |
| u8 dout[3]; |
| u8 din[3]; |
| int nbytes = enc_reg2nbytes(reg); |
| |
| enc_set_bank(enc, reg); |
| dout[0] = CMD_RCR(reg); |
| spi_xfer(enc->slave, nbytes * 8, dout, din, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| return din[nbytes-1]; |
| } |
| |
| /* |
| * Read a L/H register pair and return a word. |
| * Must be called with the L register's address. |
| */ |
| static u16 enc_r16(enc_dev_t *enc, const u16 reg) |
| { |
| u8 dout[3]; |
| u8 din[3]; |
| u16 result; |
| int nbytes = enc_reg2nbytes(reg); |
| |
| enc_set_bank(enc, reg); |
| dout[0] = CMD_RCR(reg); |
| spi_xfer(enc->slave, nbytes * 8, dout, din, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| result = din[nbytes-1]; |
| dout[0]++; /* next register */ |
| spi_xfer(enc->slave, nbytes * 8, dout, din, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| result |= din[nbytes-1] << 8; |
| return result; |
| } |
| |
| /* |
| * Write a byte register |
| */ |
| static void enc_w8(enc_dev_t *enc, const u16 reg, const u8 data) |
| { |
| u8 dout[2]; |
| |
| enc_set_bank(enc, reg); |
| dout[0] = CMD_WCR(reg); |
| dout[1] = data; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| } |
| |
| /* |
| * Write a L/H register pair. |
| * Must be called with the L register's address. |
| */ |
| static void enc_w16(enc_dev_t *enc, const u16 reg, const u16 data) |
| { |
| u8 dout[2]; |
| |
| enc_set_bank(enc, reg); |
| dout[0] = CMD_WCR(reg); |
| dout[1] = data; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| dout[0]++; /* next register */ |
| dout[1] = data >> 8; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| } |
| |
| /* |
| * Write a byte register, verify and retry |
| */ |
| static void enc_w8_retry(enc_dev_t *enc, const u16 reg, const u8 data, const int c) |
| { |
| u8 dout[2]; |
| u8 readback; |
| int i; |
| |
| enc_set_bank(enc, reg); |
| for (i = 0; i < c; i++) { |
| dout[0] = CMD_WCR(reg); |
| dout[1] = data; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| readback = enc_r8(enc, reg); |
| if (readback == data) |
| break; |
| /* wait 1ms */ |
| udelay(1000); |
| } |
| if (i == c) { |
| printf("%s: write reg 0x%03x failed\n", enc->dev->name, reg); |
| } |
| } |
| |
| /* |
| * Read ENC RAM into buffer |
| */ |
| static void enc_rbuf(enc_dev_t *enc, const u16 length, u8 *buf) |
| { |
| u8 dout[1]; |
| |
| dout[0] = CMD_RBM; |
| spi_xfer(enc->slave, 8, dout, NULL, SPI_XFER_BEGIN); |
| spi_xfer(enc->slave, length * 8, NULL, buf, SPI_XFER_END); |
| #ifdef DEBUG |
| puts("Rx:\n"); |
| print_buffer(0, buf, 1, length, 0); |
| #endif |
| } |
| |
| /* |
| * Write buffer into ENC RAM |
| */ |
| static void enc_wbuf(enc_dev_t *enc, const u16 length, const u8 *buf, const u8 control) |
| { |
| u8 dout[2]; |
| dout[0] = CMD_WBM; |
| dout[1] = control; |
| spi_xfer(enc->slave, 2 * 8, dout, NULL, SPI_XFER_BEGIN); |
| spi_xfer(enc->slave, length * 8, buf, NULL, SPI_XFER_END); |
| #ifdef DEBUG |
| puts("Tx:\n"); |
| print_buffer(0, buf, 1, length, 0); |
| #endif |
| } |
| |
| /* |
| * Try to claim the SPI bus. |
| * Print error message on failure. |
| */ |
| static int enc_claim_bus(enc_dev_t *enc) |
| { |
| int rc = spi_claim_bus(enc->slave); |
| if (rc) |
| printf("%s: failed to claim SPI bus\n", enc->dev->name); |
| return rc; |
| } |
| |
| /* |
| * Release previously claimed SPI bus. |
| * This function is mainly for symmetry to enc_claim_bus(). |
| * Let the toolchain decide to inline it... |
| */ |
| static void enc_release_bus(enc_dev_t *enc) |
| { |
| spi_release_bus(enc->slave); |
| } |
| |
| /* |
| * Read PHY register |
| */ |
| static u16 enc_phy_read(enc_dev_t *enc, const u8 addr) |
| { |
| uint64_t etime; |
| u8 status; |
| |
| enc_w8(enc, CTL_REG_MIREGADR, addr); |
| enc_w8(enc, CTL_REG_MICMD, ENC_MICMD_MIIRD); |
| /* 1 second timeout - only happens on hardware problem */ |
| etime = get_ticks() + get_tbclk(); |
| /* poll MISTAT.BUSY bit until operation is complete */ |
| do |
| { |
| status = enc_r8(enc, CTL_REG_MISTAT); |
| } while (get_ticks() <= etime && (status & ENC_MISTAT_BUSY)); |
| if (status & ENC_MISTAT_BUSY) { |
| printf("%s: timeout reading phy\n", enc->dev->name); |
| return 0; |
| } |
| enc_w8(enc, CTL_REG_MICMD, 0); |
| return enc_r16(enc, CTL_REG_MIRDL); |
| } |
| |
| /* |
| * Write PHY register |
| */ |
| static void enc_phy_write(enc_dev_t *enc, const u8 addr, const u16 data) |
| { |
| uint64_t etime; |
| u8 status; |
| |
| enc_w8(enc, CTL_REG_MIREGADR, addr); |
| enc_w16(enc, CTL_REG_MIWRL, data); |
| /* 1 second timeout - only happens on hardware problem */ |
| etime = get_ticks() + get_tbclk(); |
| /* poll MISTAT.BUSY bit until operation is complete */ |
| do |
| { |
| status = enc_r8(enc, CTL_REG_MISTAT); |
| } while (get_ticks() <= etime && (status & ENC_MISTAT_BUSY)); |
| if (status & ENC_MISTAT_BUSY) { |
| printf("%s: timeout writing phy\n", enc->dev->name); |
| return; |
| } |
| } |
| |
| /* |
| * Verify link status, wait if necessary |
| * |
| * Note: with a 10 MBit/s only PHY there is no autonegotiation possible, |
| * half/full duplex is a pure setup matter. For the time being, this driver |
| * will setup in half duplex mode only. |
| */ |
| static int enc_phy_link_wait(enc_dev_t *enc) |
| { |
| u16 status; |
| int duplex; |
| uint64_t etime; |
| |
| #ifdef CONFIG_ENC_SILENTLINK |
| /* check if we have a link, then just return */ |
| status = enc_phy_read(enc, PHY_REG_PHSTAT1); |
| if (status & ENC_PHSTAT1_LLSTAT) |
| return 0; |
| #endif |
| |
| /* wait for link with 1 second timeout */ |
| etime = get_ticks() + get_tbclk(); |
| while (get_ticks() <= etime) { |
| status = enc_phy_read(enc, PHY_REG_PHSTAT1); |
| if (status & ENC_PHSTAT1_LLSTAT) { |
| /* now we have a link */ |
| status = enc_phy_read(enc, PHY_REG_PHSTAT2); |
| duplex = (status & ENC_PHSTAT2_DPXSTAT) ? 1 : 0; |
| printf("%s: link up, 10Mbps %s-duplex\n", |
| enc->dev->name, duplex ? "full" : "half"); |
| return 0; |
| } |
| udelay(1000); |
| } |
| |
| /* timeout occured */ |
| printf("%s: link down\n", enc->dev->name); |
| return 1; |
| } |
| |
| /* |
| * This function resets the receiver only. |
| */ |
| static void enc_reset_rx(enc_dev_t *enc) |
| { |
| u8 econ1; |
| |
| econ1 = enc_r8(enc, CTL_REG_ECON1); |
| if ((econ1 & ENC_ECON1_RXRST) == 0) { |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_RXRST); |
| enc->rx_reset_counter = RX_RESET_COUNTER; |
| } |
| } |
| |
| /* |
| * Reset receiver and reenable it. |
| */ |
| static void enc_reset_rx_call(enc_dev_t *enc) |
| { |
| enc_bclr(enc, CTL_REG_ECON1, ENC_ECON1_RXRST); |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_RXEN); |
| } |
| |
| /* |
| * Copy a packet from the receive ring and forward it to |
| * the protocol stack. |
| */ |
| static void enc_receive(enc_dev_t *enc) |
| { |
| u8 *packet = (u8 *)NetRxPackets[0]; |
| u16 pkt_len; |
| u16 copy_len; |
| u16 status; |
| u8 eir_reg; |
| u8 pkt_cnt = 0; |
| u16 rxbuf_rdpt; |
| u8 hbuf[6]; |
| |
| enc_w16(enc, CTL_REG_ERDPTL, enc->next_pointer); |
| do { |
| enc_rbuf(enc, 6, hbuf); |
| enc->next_pointer = hbuf[0] | (hbuf[1] << 8); |
| pkt_len = hbuf[2] | (hbuf[3] << 8); |
| status = hbuf[4] | (hbuf[5] << 8); |
| debug("next_pointer=$%04x pkt_len=%u status=$%04x\n", |
| enc->next_pointer, pkt_len, status); |
| if (pkt_len <= ENC_MAX_FRM_LEN) |
| copy_len = pkt_len; |
| else |
| copy_len = 0; |
| if ((status & (1L << 7)) == 0) /* check Received Ok bit */ |
| copy_len = 0; |
| /* check if next pointer is resonable */ |
| if (enc->next_pointer >= ENC_TX_BUF_START) |
| copy_len = 0; |
| if (copy_len > 0) { |
| enc_rbuf(enc, copy_len, packet); |
| } |
| /* advance read pointer to next pointer */ |
| enc_w16(enc, CTL_REG_ERDPTL, enc->next_pointer); |
| /* decrease packet counter */ |
| enc_bset(enc, CTL_REG_ECON2, ENC_ECON2_PKTDEC); |
| /* |
| * Only odd values should be written to ERXRDPTL, |
| * see errata B4 pt.13 |
| */ |
| rxbuf_rdpt = enc->next_pointer - 1; |
| if ((rxbuf_rdpt < enc_r16(enc, CTL_REG_ERXSTL)) || |
| (rxbuf_rdpt > enc_r16(enc, CTL_REG_ERXNDL))) { |
| enc_w16(enc, CTL_REG_ERXRDPTL, |
| enc_r16(enc, CTL_REG_ERXNDL)); |
| } else { |
| enc_w16(enc, CTL_REG_ERXRDPTL, rxbuf_rdpt); |
| } |
| /* read pktcnt */ |
| pkt_cnt = enc_r8(enc, CTL_REG_EPKTCNT); |
| if (copy_len == 0) { |
| eir_reg = enc_r8(enc, CTL_REG_EIR); |
| enc_reset_rx(enc); |
| printf("%s: receive copy_len=0\n", enc->dev->name); |
| continue; |
| } |
| /* |
| * Because NetReceive() might call enc_send(), we need to |
| * release the SPI bus, call NetReceive(), reclaim the bus |
| */ |
| enc_release_bus(enc); |
| NetReceive(packet, pkt_len); |
| if (enc_claim_bus(enc)) |
| return; |
| eir_reg = enc_r8(enc, CTL_REG_EIR); |
| } while (pkt_cnt); |
| /* Use EPKTCNT not EIR.PKTIF flag, see errata pt. 6 */ |
| } |
| |
| /* |
| * Poll for completely received packets. |
| */ |
| static void enc_poll(enc_dev_t *enc) |
| { |
| u8 eir_reg; |
| u8 estat_reg; |
| u8 pkt_cnt; |
| |
| #ifdef CONFIG_USE_IRQ |
| /* clear global interrupt enable bit in enc28j60 */ |
| enc_bclr(enc, CTL_REG_EIE, ENC_EIE_INTIE); |
| #endif |
| estat_reg = enc_r8(enc, CTL_REG_ESTAT); |
| eir_reg = enc_r8(enc, CTL_REG_EIR); |
| if (eir_reg & ENC_EIR_TXIF) { |
| /* clear TXIF bit in EIR */ |
| enc_bclr(enc, CTL_REG_EIR, ENC_EIR_TXIF); |
| } |
| /* We have to use pktcnt and not pktif bit, see errata pt. 6 */ |
| pkt_cnt = enc_r8(enc, CTL_REG_EPKTCNT); |
| if (pkt_cnt > 0) { |
| if ((eir_reg & ENC_EIR_PKTIF) == 0) { |
| debug("enc_poll: pkt cnt > 0, but pktif not set\n"); |
| } |
| enc_receive(enc); |
| /* |
| * clear PKTIF bit in EIR, this should not need to be done |
| * but it seems like we get problems if we do not |
| */ |
| enc_bclr(enc, CTL_REG_EIR, ENC_EIR_PKTIF); |
| } |
| if (eir_reg & ENC_EIR_RXERIF) { |
| printf("%s: rx error\n", enc->dev->name); |
| enc_bclr(enc, CTL_REG_EIR, ENC_EIR_RXERIF); |
| } |
| if (eir_reg & ENC_EIR_TXERIF) { |
| printf("%s: tx error\n", enc->dev->name); |
| enc_bclr(enc, CTL_REG_EIR, ENC_EIR_TXERIF); |
| } |
| #ifdef CONFIG_USE_IRQ |
| /* set global interrupt enable bit in enc28j60 */ |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_INTIE); |
| #endif |
| } |
| |
| /* |
| * Completely Reset the ENC |
| */ |
| static void enc_reset(enc_dev_t *enc) |
| { |
| u8 dout[1]; |
| |
| dout[0] = CMD_SRC; |
| spi_xfer(enc->slave, 8, dout, NULL, |
| SPI_XFER_BEGIN | SPI_XFER_END); |
| /* sleep 1 ms. See errata pt. 2 */ |
| udelay(1000); |
| } |
| |
| /* |
| * Initialisation data for most of the ENC registers |
| */ |
| static const u16 enc_initdata[] = { |
| /* |
| * Setup the buffer space. The reset values are valid for the |
| * other pointers. |
| * |
| * We shall not write to ERXST, see errata pt. 5. Instead we |
| * have to make sure that ENC_RX_BUS_START is 0. |
| */ |
| CTL_REG_ERXSTL, ENC_RX_BUF_START, |
| CTL_REG_ERXSTH, ENC_RX_BUF_START >> 8, |
| CTL_REG_ERXNDL, ENC_RX_BUF_END, |
| CTL_REG_ERXNDH, ENC_RX_BUF_END >> 8, |
| CTL_REG_ERDPTL, ENC_RX_BUF_START, |
| CTL_REG_ERDPTH, ENC_RX_BUF_START >> 8, |
| /* |
| * Set the filter to receive only good-CRC, unicast and broadcast |
| * frames. |
| * Note: some DHCP servers return their answers as broadcasts! |
| * So its unwise to remove broadcast from this. This driver |
| * might incur receiver overruns with packet loss on a broadcast |
| * flooded network. |
| */ |
| CTL_REG_ERXFCON, ENC_RFR_BCEN | ENC_RFR_UCEN | ENC_RFR_CRCEN, |
| |
| /* enable MAC to receive frames */ |
| CTL_REG_MACON1, |
| ENC_MACON1_MARXEN | ENC_MACON1_TXPAUS | ENC_MACON1_RXPAUS, |
| |
| /* configure pad, tx-crc and duplex */ |
| CTL_REG_MACON3, |
| ENC_MACON3_PADCFG0 | ENC_MACON3_TXCRCEN | |
| ENC_MACON3_FRMLNEN, |
| |
| /* Allow infinite deferals if the medium is continously busy */ |
| CTL_REG_MACON4, ENC_MACON4_DEFER, |
| |
| /* Late collisions occur beyond 63 bytes */ |
| CTL_REG_MACLCON2, 63, |
| |
| /* |
| * Set (low byte) Non-Back-to_Back Inter-Packet Gap. |
| * Recommended 0x12 |
| */ |
| CTL_REG_MAIPGL, 0x12, |
| |
| /* |
| * Set (high byte) Non-Back-to_Back Inter-Packet Gap. |
| * Recommended 0x0c for half-duplex. Nothing for full-duplex |
| */ |
| CTL_REG_MAIPGH, 0x0C, |
| |
| /* set maximum frame length */ |
| CTL_REG_MAMXFLL, ENC_MAX_FRM_LEN, |
| CTL_REG_MAMXFLH, ENC_MAX_FRM_LEN >> 8, |
| |
| /* |
| * Set MAC back-to-back inter-packet gap. |
| * Recommended 0x12 for half duplex |
| * and 0x15 for full duplex. |
| */ |
| CTL_REG_MABBIPG, 0x12, |
| |
| /* end of table */ |
| 0xffff |
| }; |
| |
| /* |
| * Wait for the XTAL oscillator to become ready |
| */ |
| static int enc_clock_wait(enc_dev_t *enc) |
| { |
| uint64_t etime; |
| |
| /* one second timeout */ |
| etime = get_ticks() + get_tbclk(); |
| |
| /* |
| * Wait for CLKRDY to become set (i.e., check that we can |
| * communicate with the ENC) |
| */ |
| do |
| { |
| if (enc_r8(enc, CTL_REG_ESTAT) & ENC_ESTAT_CLKRDY) |
| return 0; |
| } while (get_ticks() <= etime); |
| |
| printf("%s: timeout waiting for CLKRDY\n", enc->dev->name); |
| return -1; |
| } |
| |
| /* |
| * Write the MAC address into the ENC |
| */ |
| static int enc_write_macaddr(enc_dev_t *enc) |
| { |
| unsigned char *p = enc->dev->enetaddr; |
| |
| enc_w8_retry(enc, CTL_REG_MAADR5, *p++, 5); |
| enc_w8_retry(enc, CTL_REG_MAADR4, *p++, 5); |
| enc_w8_retry(enc, CTL_REG_MAADR3, *p++, 5); |
| enc_w8_retry(enc, CTL_REG_MAADR2, *p++, 5); |
| enc_w8_retry(enc, CTL_REG_MAADR1, *p++, 5); |
| enc_w8_retry(enc, CTL_REG_MAADR0, *p, 5); |
| return 0; |
| } |
| |
| /* |
| * Setup most of the ENC registers |
| */ |
| static int enc_setup(enc_dev_t *enc) |
| { |
| u16 phid1 = 0; |
| u16 phid2 = 0; |
| const u16 *tp; |
| |
| /* reset enc struct values */ |
| enc->next_pointer = ENC_RX_BUF_START; |
| enc->rx_reset_counter = RX_RESET_COUNTER; |
| enc->bank = 0xff; /* invalidate current bank in enc28j60 */ |
| |
| /* verify PHY identification */ |
| phid1 = enc_phy_read(enc, PHY_REG_PHID1); |
| phid2 = enc_phy_read(enc, PHY_REG_PHID2) & ENC_PHID2_MASK; |
| if (phid1 != ENC_PHID1_VALUE || phid2 != ENC_PHID2_VALUE) { |
| printf("%s: failed to identify PHY. Found %04x:%04x\n", |
| enc->dev->name, phid1, phid2); |
| return -1; |
| } |
| |
| /* now program registers */ |
| for (tp = enc_initdata; *tp != 0xffff; tp += 2) |
| enc_w8_retry(enc, tp[0], tp[1], 10); |
| |
| /* |
| * Prevent automatic loopback of data beeing transmitted by setting |
| * ENC_PHCON2_HDLDIS |
| */ |
| enc_phy_write(enc, PHY_REG_PHCON2, (1<<8)); |
| |
| /* |
| * LEDs configuration |
| * LEDA: LACFG = 0100 -> display link status |
| * LEDB: LBCFG = 0111 -> display TX & RX activity |
| * STRCH = 1 -> LED pulses |
| */ |
| enc_phy_write(enc, PHY_REG_PHLCON, 0x0472); |
| |
| /* Reset PDPXMD-bit => half duplex */ |
| enc_phy_write(enc, PHY_REG_PHCON1, 0); |
| |
| #ifdef CONFIG_USE_IRQ |
| /* enable interrupts */ |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_PKTIE); |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_TXIE); |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_RXERIE); |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_TXERIE); |
| enc_bset(enc, CTL_REG_EIE, ENC_EIE_INTIE); |
| #endif |
| |
| return 0; |
| } |
| |
| /* |
| * Check if ENC has been initialized. |
| * If not, try to initialize it. |
| * Remember initialized state in struct. |
| */ |
| static int enc_initcheck(enc_dev_t *enc, const enum enc_initstate requiredstate) |
| { |
| if (enc->initstate >= requiredstate) |
| return 0; |
| |
| if (enc->initstate < setupdone) { |
| /* Initialize the ENC only */ |
| enc_reset(enc); |
| /* if any of functions fails, skip the rest and return an error */ |
| if (enc_clock_wait(enc) || enc_setup(enc) || enc_write_macaddr(enc)) { |
| return -1; |
| } |
| enc->initstate = setupdone; |
| } |
| /* if that's all we need, return here */ |
| if (enc->initstate >= requiredstate) |
| return 0; |
| |
| /* now wait for link ready condition */ |
| if (enc_phy_link_wait(enc)) { |
| return -1; |
| } |
| enc->initstate = linkready; |
| return 0; |
| } |
| |
| #if defined(CONFIG_CMD_MII) |
| /* |
| * Read a PHY register. |
| * |
| * This function is registered with miiphy_register(). |
| */ |
| int enc_miiphy_read(const char *devname, u8 phy_adr, u8 reg, u16 *value) |
| { |
| struct eth_device *dev = eth_get_dev_by_name(devname); |
| enc_dev_t *enc; |
| |
| if (!dev || phy_adr != 0) |
| return -1; |
| |
| enc = dev->priv; |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, setupdone)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| *value = enc_phy_read(enc, reg); |
| enc_release_bus(enc); |
| return 0; |
| } |
| |
| /* |
| * Write a PHY register. |
| * |
| * This function is registered with miiphy_register(). |
| */ |
| int enc_miiphy_write(const char *devname, u8 phy_adr, u8 reg, u16 value) |
| { |
| struct eth_device *dev = eth_get_dev_by_name(devname); |
| enc_dev_t *enc; |
| |
| if (!dev || phy_adr != 0) |
| return -1; |
| |
| enc = dev->priv; |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, setupdone)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| enc_phy_write(enc, reg, value); |
| enc_release_bus(enc); |
| return 0; |
| } |
| #endif |
| |
| /* |
| * Write hardware (MAC) address. |
| * |
| * This function entered into eth_device structure. |
| */ |
| static int enc_write_hwaddr(struct eth_device *dev) |
| { |
| enc_dev_t *enc = dev->priv; |
| |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, setupdone)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| enc_release_bus(enc); |
| return 0; |
| } |
| |
| /* |
| * Initialize ENC28J60 for use. |
| * |
| * This function entered into eth_device structure. |
| */ |
| static int enc_init(struct eth_device *dev, bd_t *bis) |
| { |
| enc_dev_t *enc = dev->priv; |
| |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, linkready)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| /* enable receive */ |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_RXEN); |
| enc_release_bus(enc); |
| return 0; |
| } |
| |
| /* |
| * Check for received packets. |
| * |
| * This function entered into eth_device structure. |
| */ |
| static int enc_recv(struct eth_device *dev) |
| { |
| enc_dev_t *enc = dev->priv; |
| |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, linkready)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| /* Check for dead receiver */ |
| if (enc->rx_reset_counter > 0) |
| enc->rx_reset_counter--; |
| else |
| enc_reset_rx_call(enc); |
| enc_poll(enc); |
| enc_release_bus(enc); |
| return 0; |
| } |
| |
| /* |
| * Send a packet. |
| * |
| * This function entered into eth_device structure. |
| * |
| * Should we wait here until we have a Link? Or shall we leave that to |
| * protocol retries? |
| */ |
| static int enc_send( |
| struct eth_device *dev, |
| volatile void *packet, |
| int length) |
| { |
| enc_dev_t *enc = dev->priv; |
| |
| if (enc_claim_bus(enc)) |
| return -1; |
| if (enc_initcheck(enc, linkready)) { |
| enc_release_bus(enc); |
| return -1; |
| } |
| /* setup transmit pointers */ |
| enc_w16(enc, CTL_REG_EWRPTL, ENC_TX_BUF_START); |
| enc_w16(enc, CTL_REG_ETXNDL, length + ENC_TX_BUF_START); |
| enc_w16(enc, CTL_REG_ETXSTL, ENC_TX_BUF_START); |
| /* write packet to ENC */ |
| enc_wbuf(enc, length, (u8 *) packet, 0x00); |
| /* |
| * Check that the internal transmit logic has not been altered |
| * by excessive collisions. Reset transmitter if so. |
| * See Errata B4 12 and 14. |
| */ |
| if (enc_r8(enc, CTL_REG_EIR) & ENC_EIR_TXERIF) { |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_TXRST); |
| enc_bclr(enc, CTL_REG_ECON1, ENC_ECON1_TXRST); |
| } |
| enc_bclr(enc, CTL_REG_EIR, (ENC_EIR_TXERIF | ENC_EIR_TXIF)); |
| /* start transmitting */ |
| enc_bset(enc, CTL_REG_ECON1, ENC_ECON1_TXRTS); |
| enc_release_bus(enc); |
| return 0; |
| } |
| |
| /* |
| * Finish use of ENC. |
| * |
| * This function entered into eth_device structure. |
| */ |
| static void enc_halt(struct eth_device *dev) |
| { |
| enc_dev_t *enc = dev->priv; |
| |
| if (enc_claim_bus(enc)) |
| return; |
| /* Just disable receiver */ |
| enc_bclr(enc, CTL_REG_ECON1, ENC_ECON1_RXEN); |
| enc_release_bus(enc); |
| } |
| |
| /* |
| * This is the only exported function. |
| * |
| * It may be called several times with different bus:cs combinations. |
| */ |
| int enc28j60_initialize(unsigned int bus, unsigned int cs, |
| unsigned int max_hz, unsigned int mode) |
| { |
| struct eth_device *dev; |
| enc_dev_t *enc; |
| |
| /* try to allocate, check and clear eth_device object */ |
| dev = malloc(sizeof(*dev)); |
| if (!dev) { |
| return -1; |
| } |
| memset(dev, 0, sizeof(*dev)); |
| |
| /* try to allocate, check and clear enc_dev_t object */ |
| enc = malloc(sizeof(*enc)); |
| if (!enc) { |
| free(dev); |
| return -1; |
| } |
| memset(enc, 0, sizeof(*enc)); |
| |
| /* try to setup the SPI slave */ |
| enc->slave = spi_setup_slave(bus, cs, max_hz, mode); |
| if (!enc->slave) { |
| printf("enc28j60: invalid SPI device %i:%i\n", bus, cs); |
| free(enc); |
| free(dev); |
| return -1; |
| } |
| |
| enc->dev = dev; |
| /* now fill the eth_device object */ |
| dev->priv = enc; |
| dev->init = enc_init; |
| dev->halt = enc_halt; |
| dev->send = enc_send; |
| dev->recv = enc_recv; |
| dev->write_hwaddr = enc_write_hwaddr; |
| sprintf(dev->name, "enc%i.%i", bus, cs); |
| eth_register(dev); |
| #if defined(CONFIG_CMD_MII) |
| miiphy_register(dev->name, enc_miiphy_read, enc_miiphy_write); |
| #endif |
| return 0; |
| } |