/* $NetBSD: lance.c,v 1.7 2018/09/04 15:08:30 riastradh Exp $ */
/*-
* Copyright (c) 2004 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by UCHIYAMA Yasushi.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* LANCE driver for EWS4800/360 */
#include <lib/libsa/stand.h>
#include <lib/libkern/libkern.h>
#include <dev/ic/am7990reg.h>
#include <dev/ic/lancereg.h>
#include "local.h"
/* Register Address Pointer */
#define LANCE_RAP ((volatile uint16_t *)0xbe400006)
/* Register Data Port */
#define LANCE_RDP ((volatile uint16_t *)0xbe400000)
#define RX_DESC_NUM 8
#define TX_DESC_NUM 8
#define TX_BUFSIZE 0x1000
#define RX_BUFSIZE 0x1000
struct {
struct leinit leinit;
struct lermd lermd[RX_DESC_NUM];
struct letmd letmd[TX_DESC_NUM];
uint8_t eaddr[6];
uint8_t txdata[TX_BUFSIZE] __attribute__((__aligned__(0x1000)));
uint8_t rxdata[RX_BUFSIZE] __attribute__((__aligned__(0x1000)));
} lance_mem __attribute__((__aligned__(64)));
bool lance_init(void);
void lance_eaddr(uint8_t *);
bool lance_get(void *, size_t);
bool lance_put(void *, size_t);
void lance_setup(void);
bool lance_set_initblock(struct leinit *);
bool lance_do_initialize(void);
bool lance_test(void);
bool lance_internal_loopback_test(bool);
void lance_internal_loopback_setup(bool);
void lance_internal_loopback_testdata(void);
bool lance_internal_loopback_data_check(bool);
bool __poll_interrupt(void);
bool __poll_lance_c0(uint16_t);
bool
lance_init(void)
{
lance_setup();
if (!lance_set_initblock(&lance_mem.leinit))
return false;
if (!lance_do_initialize())
return false;
*LANCE_RDP = LE_C0_STRT;
return true;
}
void
lance_eaddr(uint8_t *p)
{
int i;
for (i = 0; i < 6; i++)
p[i] = lance_mem.eaddr[i];
}
bool
lance_get(void *data, size_t len)
{
static int current;
struct lermd *rmd;
int i, j, k, n;
int start, end;
uint8_t *q, *p = data, *p_end = p + len;
while ((*LANCE_RDP & (LE_C0_RINT | LE_C0_INTR)) == 0)
;
*LANCE_RDP = LE_C0_RINT;
start = end = -1;
n = 0;
for (i = 0; i < 8; i++) {
rmd = &lance_mem.lermd[(current + i) & 0x7];
if (rmd->rmd1_bits & LE_R1_STP)
start = i;
if (rmd->rmd1_bits & LE_R1_ENP) {
end = i;
n = rmd->rmd3; /* total amount of packet */
break;
}
}
#ifdef DEBUG
printf("%s: %d [%d,%d] %d\n", __func__, len, start, end, n);
#endif
if (start < 0 || end < 0)
return false;
for (i = start; i <= end; i++) {
rmd = &lance_mem.lermd[(current + i) & 0x7];
q = (uint8_t *)((rmd->rmd1_hadr << 16) | rmd->rmd0 |
0xa0000000);
j = i == end ? n : -rmd->rmd2;
for (k = 0; k < j; k++)
if (p < p_end)
*p++ = *q++;
n -= j;
rmd->rmd1_bits = LE_R1_OWN; /* return to LANCE */
}
current = (current + i) & 0x7;
return true;
}
bool
lance_put(void *data, size_t len)
{
static int current;
struct letmd *tmd;
uint16_t r;
uint8_t *p, *q = data;
int i, j, n, start;
start = current;
tmd = &lance_mem.letmd[current];
tmd->tmd1_bits = LE_T1_STP;
for (i = 0; i < 8; i++) {
current = (current + 1) & 0x7;
n = uimin(len, 512);
p = (uint8_t *)((tmd->tmd1_hadr << 16) | tmd->tmd0 |
0xa0000000);
for (j = 0; j < n; j++)
*p++ = *q++;
len -= n;
#if 1
tmd->tmd2 = -uimax(n, 64) | 0xf000;
#else
tmd->tmd2 = -n | 0xf000;
#endif
tmd->tmd3 = 0;
if (len == 0) {
tmd->tmd1_bits |= LE_T1_ENP;
break;
}
tmd = &lance_mem.letmd[current];
}
n = i + 1;
for (i = 0; i < n; i++) {
tmd = &lance_mem.letmd[start + i];
*LANCE_RDP = LE_C0_INEA;
tmd->tmd1_bits |= LE_T1_OWN;
j = 0;
do {
*LANCE_RAP;
r = *LANCE_RDP;
if (r & LE_C0_ERR) {
printf("Error. CSR0=%x\n", r);
return false;
}
if (j++ > 0xa0000) {
printf("Timeout CSR0=%x\n", r);
return false;
}
} while ((r & (LE_C0_TINT | LE_C0_INTR)) == 0);
*LANCE_RDP = LE_C0_TINT;
}
for (i = 0; i < n; i++) {
uint8_t *bits = &lance_mem.letmd[i].tmd1_bits;
if (*bits & LE_T1_OWN || *bits & LE_T1_ERR) {
printf("desc%d not transmitted. cause=%x\n", i, *bits);
return false;
}
*bits = 0;
}
return true;
}
bool
lance_set_initblock(struct leinit *leinit)
{
uint16_t test_data[] = { 0xffff, 0xaaaa, 0x5555, 0x0000 };
uint16_t t;
uint32_t addr = (uint32_t)leinit;
int i;
/* Control and status register */
for (i = 3; i >= 0; i--) {
*LANCE_RAP = i;
if ((*LANCE_RAP & 3) != i)
goto reg_rw_error;
}
*LANCE_RDP = LE_C0_STOP; /* disable all external activity */
if (*LANCE_RDP != LE_C0_STOP)
goto reg_rw_error;
/* Low address of init block */
for (i = 0; i < 4; i++) {
t = test_data[i] & 0xfffe;
*LANCE_RAP = LE_CSR1;
*LANCE_RDP = t;
if (*LANCE_RDP != t)
goto reg_rw_error;
}
*LANCE_RDP = addr & 0xfffe;
#if DEBUG
printf("initblock low addr=%x\n", *LANCE_RDP);
#endif
/* High address of init block */
for (i = 0; i < 4; i++) {
t = test_data[i] & 0x00ff;
*LANCE_RAP = LE_CSR2;
*LANCE_RDP = t;
if (*LANCE_RDP != t)
goto reg_rw_error;
}
*LANCE_RDP = (addr >> 16) & 0x00ff;
#ifdef DEBUG
printf("initblock high addr=%x\n", *LANCE_RDP);
#endif
/* Bus master and control */
*LANCE_RAP = LE_CSR3;
*LANCE_RDP = 7;
if (*LANCE_RDP != 7)
goto reg_rw_error;
*LANCE_RAP = LE_CSR3;
*LANCE_RDP = 0;
if (*LANCE_RDP != 0)
goto reg_rw_error;
*LANCE_RDP = LE_C3_BSWP | LE_C3_BCON;
return true;
reg_rw_error:
printf("LANCE register r/w error.\n");
return false;
}
bool
lance_do_initialize(void)
{
/* Initialze LANCE */
*LANCE_RAP = LE_CSR0;
*LANCE_RDP = LE_C0_INEA | LE_C0_INIT;
/* Wait interrupt */
if (!__poll_interrupt())
return false;
*LANCE_RDP = *LANCE_RDP;
return true;
}
void
lance_setup(void)
{
struct leinit *init = &lance_mem.leinit;
struct lermd *lermd = lance_mem.lermd;
struct letmd *letmd = lance_mem.letmd;
uint32_t addr;
uint8_t *eaddr;
int i;
memset(&lance_mem, 0, sizeof lance_mem);
/* Ethernet address from NVSRAM */
eaddr = lance_mem.eaddr;
for (i = 0; i < 6; i++)
eaddr[i] = *(uint8_t *)(0xbe491008 + i * 4);
/* Init block */
init->init_mode = 0;
init->init_padr[0] = (eaddr[1] << 8) | eaddr[0];
init->init_padr[1] = (eaddr[3] << 8) | eaddr[2];
init->init_padr[2] = (eaddr[5] << 8) | eaddr[4];
/* Logical address filter */
for (i = 0; i < 4; i++)
init->init_ladrf[i] = 0x0000;
/* Location of Rx descriptor ring */
addr = (uint32_t)lermd;
init->init_rdra = addr & 0xffff;
init->init_rlen = ((ffs(RX_DESC_NUM) - 1) << 13) |
((addr >> 16) & 0xff);
/* Location of Tx descriptor ring */
addr = (uint32_t)letmd;
init->init_tdra = addr & 0xffff;
init->init_tlen = ((ffs(RX_DESC_NUM) - 1) << 13) |
((addr >> 16) & 0xff);
/* Rx descriptor */
addr = (uint32_t)lance_mem.rxdata;
for (i = 0; i < RX_DESC_NUM; i++, lermd++) {
lermd->rmd0 = (addr & 0xffff) + i * 512; /* data block size */
lermd->rmd1_hadr = (addr >> 16) & 0xff;
lermd->rmd1_bits = LE_R1_OWN;
lermd->rmd2 = -512;
lermd->rmd3 = 0;
}
/* Tx descriptor */
addr = (uint32_t)lance_mem.txdata;
for (i = 0; i < TX_DESC_NUM; i++, letmd++) {
letmd->tmd0 = (addr & 0xffff) + i * 512; /* data block size */
letmd->tmd1_hadr = (addr >> 16) & 0xff;
letmd->tmd1_bits = 0;
letmd->tmd2 = 0;
letmd->tmd3 = 0;
}
}
/*
* Internal loopback test.
*/
bool
lance_test(void)
{
/* Internal loop back test. (no CRC) */
if (!lance_internal_loopback_test(false))
return false;
/* Internal loop back test. (with CRC) */
if (!lance_internal_loopback_test(true))
return false;
return true;
}
bool
lance_internal_loopback_test(bool crc)
{
lance_internal_loopback_setup(crc);
if (!lance_set_initblock(&lance_mem.leinit))
return false;
if (!lance_do_initialize())
return false;
/* Transmit Start */
*LANCE_RAP = LE_CSR0; /* Control and status register */
*LANCE_RDP = LE_C0_INEA | LE_C0_STRT;
/* Check trasmited data. */
return lance_internal_loopback_data_check(crc);
}
void
lance_internal_loopback_setup(bool crc)
{
struct leinit *init = &lance_mem.leinit;
struct lermd *lermd = lance_mem.lermd;
struct letmd *letmd = lance_mem.letmd;
uint32_t addr;
int i;
memset(&lance_mem, 0, sizeof lance_mem);
/* Init block */
init->init_mode = LE_C15_INTL | LE_C15_LOOP;
if (!crc)
init->init_mode |= LE_C15_DXMTFCS;
init->init_padr[0] = 0x0000;
init->init_padr[1] = 0x8400;
init->init_padr[2] = 0x0000;
for (i = 0; i < 4; i++)
init->init_ladrf[i] = 0x0000;
addr = (uint32_t)lermd;
init->init_rdra = addr & 0xffff;
init->init_rlen = (ffs(RX_DESC_NUM) << 13) | ((addr >> 16) & 0xff);
addr = (uint32_t)letmd;
init->init_tdra = addr & 0xffff;
init->init_tlen = (ffs(RX_DESC_NUM) << 13) | ((addr >> 16) & 0xff);
/* Rx descriptor */
addr = (uint32_t)lance_mem.rxdata;
for (i = 0; i < RX_DESC_NUM; i++, lermd++) {
lermd->rmd0 = (addr & 0xffff) + i * 64; /* data block size */
lermd->rmd1_hadr = (addr >> 16) & 0xff;
lermd->rmd1_bits = LE_R1_OWN;
lermd->rmd2 = -64;
lermd->rmd3 = 0;
}
/* Tx descriptor */
addr = (uint32_t)lance_mem.txdata;
for (i = 0; i < TX_DESC_NUM; i++, letmd++) {
letmd->tmd0 = (addr & 0xffff) + i * 64; /* data block size */
letmd->tmd1_hadr = (addr >> 16) & 0xff;
letmd->tmd1_bits = LE_T1_STP | LE_T1_ENP;
if (crc)
letmd->tmd2 = -28;
else
letmd->tmd2 = -32;
letmd->tmd3 = 0;
}
lance_internal_loopback_testdata();
}
void
lance_internal_loopback_testdata(void)
{
uint16_t test_data[] = {
0x55aa, 0xff00, 0x0102, 0x0304, 0x0506, 0x0708, 0x0910,
0x40db, 0xdfcf, /* CRC */
0x23dc, 0x23dc, 0x1918, 0x1716, 0x1514, 0x1312, 0x1110,
0x7081, 0x90cb, /* CRC */
0x6699, 0xaa55, 0x0515, 0x2535, 0x4555, 0x6575, 0x8595,
0x55f6, 0xa448, /* CRC */
0x4e4e, 0x5a5a, 0x6969, 0x7878, 0x0f0f, 0x1e1e, 0x2d2d,
0xa548, 0x7404, /* CRC */
};
uint16_t test_header[] = {
0x0000, 0x0084, 0x0000, /* dst */
0x0000, 0x0084, 0x0000, /* src */
0x000e
};
uint16_t *p = (uint16_t *)lance_mem.txdata;
int i, j, k;
for (i = 0; i < 2; i++) { /* 64byte * 8 */
uint16_t *r = test_data;
for (j = 0; j < 4; j++) { /* 64byte * 4 */
uint16_t *q = test_header;
for (k = 0; k < 7; k++) /* 14byte */
*p++ = *q++;
for (k = 0; k < 9; k++) /* 18byte */
*p++ = *r++;
p += 16; /* 32byte skip */
}
}
}
bool
lance_internal_loopback_data_check(bool crc_check)
{
uint32_t *p = (uint32_t *)lance_mem.txdata;
uint32_t *q = (uint32_t *)lance_mem.rxdata;
int i, j;
/* Read all data block */
for (i = 0; i < 8; i++) {
printf("block %d ", i);
lance_mem.letmd[i].tmd1_bits |= LE_T1_OWN;/* buffer is filled */
/* wait interrupt */
if (!__poll_interrupt())
goto timeout_error;
/* wait LANCE status */
if (!__poll_lance_c0(LE_C0_RINT | LE_C0_TINT | LE_C0_INTR |
LE_C0_INEA | LE_C0_RXON | LE_C0_TXON | LE_C0_STRT |
LE_C0_INIT))
goto timeout_error;
/* check Tx descriptor */
if (lance_mem.letmd[i].tmd1_bits & LE_T1_ERR) {
printf("tx desc error.\n");
goto tx_rx_error;
}
/* check Rx descriptor */
if (lance_mem.lermd[i].rmd1_bits & LE_R1_ERR) {
printf("rx desc error.\n");
goto tx_rx_error;
}
/* Compare transmitted data */
for (j = 0; j < 7; j++) /* first 28byte */
if (*p++ != *q++) {
printf("data error.\n");
goto tx_rx_error;
}
/* check CRC */
if (crc_check) {
printf("CRC=%x ", *p);
if (*p != *q) { /* CRC */
goto crc_error;
}
}
printf("ok.\n");
p += 9; /* 36byte skip */
q += 9;
}
return true;
timeout_error:
printf("LANCE timeout.\n");
return false;
tx_rx_error:
printf("LANCE Tx/Rx data error.\n");
return false;
crc_error:
printf("LANCE CRC error.\n");
return false;
}
bool
__poll_interrupt(void)
{
int j;
for (j = 0; j < 0x10000; j++) {
*LANCE_RAP;
if (*(volatile uint32_t *)0xbe40a008 & 1)
break;
}
if (j == 0x10000) {
printf ("interrupt timeout.\n");
return false;
}
return true;
}
bool
__poll_lance_c0(uint16_t r)
{
int j;
for (j = 0; j < 0x60000; j++)
if (*LANCE_RDP == r)
break;
if (j == 0x60000) {
printf("lance CSR0 %x != %x\n", *LANCE_RDP, r);
return false;
}
*LANCE_RDP = (LE_C0_RINT | LE_C0_TINT| LE_C0_INEA) & r;
return true;
}