NetBSD/sys/dev/ic/hscx.c
martin 5171d409a5 First step to cleanup the hardware driver <-> upper layers interface.
This now provides slightly more functionality than the FreeBSD layer1-newbus
interface. It was meant to be a simple change to one header and a few
c files, but the change rippled all through various stuff.

To prevent a change to the kernel<->userland interface right now the kernel
is now lying about card types to userland (but who cares). This will be fixed
when the userland interface changes, after layer 3 <-> layer 4 has been
fixed.

Functional changes:

Provide a clean interface for hardware drivers to attach to the upper
layers. This will need another small change in the B-channel handling
when a similar change to the layer 3 <-> layer 4 interface happens.

Avoid passing indices into global arrays of pointers around, instead pass
the pointers itself. Don't code hardware driver types by predefined magic
numbers (think LKM). Prepare for detachable drivers (think pcmcia).

While there remove some sets of function pointers always pointing to the
same function (meant to be the configurable set of D channel protocol
handlers). It is unlikely another supported D-channel protocol will fit into
that (maximal layer interface) abstraction. When we get support for another
protocol, we will need to come up with a workable interface. Besides, the
old implementation was, uhm, strange.
2001-03-24 12:40:29 +00:00

672 lines
17 KiB
C

/*
* Copyright (c) 1997, 2000 Hellmuth Michaelis. All rights reserved.
*
* 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 AUTHOR 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 AUTHOR 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.
*
*---------------------------------------------------------------------------
*
* i4b - Siemens HSCX chip (B-channel) handling
* --------------------------------------------
*
* $Id: hscx.c,v 1.2 2001/03/24 12:40:29 martin Exp $
*
* last edit-date: [Fri Jan 5 11:36:10 2001]
*
*---------------------------------------------------------------------------*/
#include <sys/param.h>
#if defined(__FreeBSD_version) && __FreeBSD_version >= 300001
#include <sys/ioccom.h>
#else
#include <sys/ioctl.h>
#endif
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <machine/stdarg.h>
#ifdef __FreeBSD__
#include <machine/clock.h>
#include <i386/isa/isa_device.h>
#else
#ifndef __bsdi__
#include <machine/bus.h>
#endif
#include <sys/device.h>
#endif
#include <sys/socket.h>
#include <net/if.h>
#if defined(__NetBSD__) && __NetBSD_Version__ >= 104230000
#include <sys/callout.h>
#endif
#ifdef __FreeBSD__
#include <machine/i4b_debug.h>
#include <machine/i4b_ioctl.h>
#include <machine/i4b_trace.h>
#else
#include <netisdn/i4b_debug.h>
#include <netisdn/i4b_ioctl.h>
#include <netisdn/i4b_trace.h>
#endif
#include <dev/ic/isic_l1.h>
#include <dev/ic/isac.h>
#include <dev/ic/hscx.h>
#include <netisdn/i4b_l1l2.h>
#include <netisdn/i4b_global.h>
#include <netisdn/i4b_mbuf.h>
/*---------------------------------------------------------------------------*
* HSCX IRQ Handler
*---------------------------------------------------------------------------*/
void
isic_hscx_irq(register struct l1_softc *sc, u_char ista, int h_chan, u_char ex_irq)
{
register l1_bchan_state_t *chan = &sc->sc_chan[h_chan];
u_char exir = 0;
int activity = -1;
u_char cmd = 0;
NDBGL1(L1_H_IRQ, "%#x", ista);
if(ex_irq)
{
/* get channel extended irq reg */
exir = HSCX_READ(h_chan, H_EXIR);
if(exir & HSCX_EXIR_RFO)
{
chan->stat_RFO++;
NDBGL1(L1_H_XFRERR, "ex_irq: receive data overflow");
}
if((exir & HSCX_EXIR_XDU) && (chan->bprot != BPROT_NONE))/* xmit data underrun */
{
chan->stat_XDU++;
NDBGL1(L1_H_XFRERR, "ex_irq: xmit data underrun");
isic_hscx_cmd(sc, h_chan, HSCX_CMDR_XRES);
if (chan->out_mbuf_head != NULL) /* don't continue to transmit this buffer */
{
i4b_Bfreembuf(chan->out_mbuf_head);
chan->out_mbuf_cur = chan->out_mbuf_head = NULL;
}
}
}
/* rx message end, end of frame */
if(ista & HSCX_ISTA_RME)
{
register int fifo_data_len;
u_char rsta;
int error = 0;
rsta = HSCX_READ(h_chan, H_RSTA);
if((rsta & 0xf0) != 0xa0)
{
if((rsta & HSCX_RSTA_VFR) == 0)
{
chan->stat_VFR++;
cmd |= (HSCX_CMDR_RHR);
NDBGL1(L1_H_XFRERR, "received invalid Frame");
error++;
}
if(rsta & HSCX_RSTA_RDO)
{
chan->stat_RDO++;
NDBGL1(L1_H_XFRERR, "receive data overflow");
error++;
}
if((rsta & HSCX_RSTA_CRC) == 0)
{
chan->stat_CRC++;
cmd |= (HSCX_CMDR_RHR);
NDBGL1(L1_H_XFRERR, "CRC check failed");
error++;
}
if(rsta & HSCX_RSTA_RAB)
{
chan->stat_RAB++;
NDBGL1(L1_H_XFRERR, "Receive message aborted");
error++;
}
}
fifo_data_len = ((HSCX_READ(h_chan, H_RBCL)) &
((sc->sc_bfifolen)-1));
if(fifo_data_len == 0)
fifo_data_len = sc->sc_bfifolen;
/* all error conditions checked, now decide and take action */
if(error == 0)
{
if(chan->in_mbuf == NULL)
{
if((chan->in_mbuf = i4b_Bgetmbuf(BCH_MAX_DATALEN)) == NULL)
panic("L1 isic_hscx_irq: RME, cannot allocate mbuf!\n");
chan->in_cbptr = chan->in_mbuf->m_data;
chan->in_len = 0;
}
fifo_data_len -= 1; /* last byte in fifo is RSTA ! */
if((chan->in_len + fifo_data_len) <= BCH_MAX_DATALEN)
{
/* read data from HSCX fifo */
HSCX_RDFIFO(h_chan, chan->in_cbptr, fifo_data_len);
cmd |= (HSCX_CMDR_RMC);
isic_hscx_cmd(sc, h_chan, cmd);
cmd = 0;
chan->in_len += fifo_data_len;
chan->rxcount += fifo_data_len;
/* setup mbuf data length */
chan->in_mbuf->m_len = chan->in_len;
chan->in_mbuf->m_pkthdr.len = chan->in_len;
if(sc->sc_trace & TRACE_B_RX)
{
i4b_trace_hdr hdr;
hdr.type = (h_chan == HSCX_CH_A ? TRC_CH_B1 : TRC_CH_B2);
hdr.dir = FROM_NT;
hdr.count = ++sc->sc_trace_bcount;
isdn_layer2_trace_ind(sc->sc_l2, &hdr, chan->in_mbuf->m_len, chan->in_mbuf->m_data);
}
(*chan->drvr_linktab->bch_rx_data_ready)(chan->drvr_linktab->unit);
activity = ACT_RX;
/* mark buffer ptr as unused */
chan->in_mbuf = NULL;
chan->in_cbptr = NULL;
chan->in_len = 0;
}
else
{
NDBGL1(L1_H_XFRERR, "RAWHDLC rx buffer overflow in RME, in_len=%d, fifolen=%d", chan->in_len, fifo_data_len);
chan->in_cbptr = chan->in_mbuf->m_data;
chan->in_len = 0;
cmd |= (HSCX_CMDR_RHR | HSCX_CMDR_RMC);
}
}
else
{
if (chan->in_mbuf != NULL)
{
i4b_Bfreembuf(chan->in_mbuf);
chan->in_mbuf = NULL;
chan->in_cbptr = NULL;
chan->in_len = 0;
}
cmd |= (HSCX_CMDR_RMC);
}
}
/* rx fifo full */
if(ista & HSCX_ISTA_RPF)
{
if(chan->in_mbuf == NULL)
{
if((chan->in_mbuf = i4b_Bgetmbuf(BCH_MAX_DATALEN)) == NULL)
panic("L1 isic_hscx_irq: RPF, cannot allocate mbuf!\n");
chan->in_cbptr = chan->in_mbuf->m_data;
chan->in_len = 0;
}
chan->rxcount += sc->sc_bfifolen;
if((chan->in_len + sc->sc_bfifolen) <= BCH_MAX_DATALEN)
{
/* read data from HSCX fifo */
HSCX_RDFIFO(h_chan, chan->in_cbptr, sc->sc_bfifolen);
chan->in_cbptr += sc->sc_bfifolen;
chan->in_len += sc->sc_bfifolen;
}
else
{
if(chan->bprot == BPROT_NONE)
{
/* setup mbuf data length */
chan->in_mbuf->m_len = chan->in_len;
chan->in_mbuf->m_pkthdr.len = chan->in_len;
if(sc->sc_trace & TRACE_B_RX)
{
i4b_trace_hdr hdr;
hdr.type = (h_chan == HSCX_CH_A ? TRC_CH_B1 : TRC_CH_B2);
hdr.dir = FROM_NT;
hdr.count = ++sc->sc_trace_bcount;
isdn_layer2_trace_ind(sc->sc_l2, &hdr,chan->in_mbuf->m_len, chan->in_mbuf->m_data);
}
/* silence detection */
if(!(isic_hscx_silence(chan->in_mbuf->m_data, chan->in_mbuf->m_len)))
activity = ACT_RX;
if(!(IF_QFULL(&chan->rx_queue)))
{
IF_ENQUEUE(&chan->rx_queue, chan->in_mbuf);
}
else
{
i4b_Bfreembuf(chan->in_mbuf);
}
/* signal upper driver that data is available */
(*chan->drvr_linktab->bch_rx_data_ready)(chan->drvr_linktab->unit);
/* alloc new buffer */
if((chan->in_mbuf = i4b_Bgetmbuf(BCH_MAX_DATALEN)) == NULL)
panic("L1 isic_hscx_irq: RPF, cannot allocate new mbuf!\n");
/* setup new data ptr */
chan->in_cbptr = chan->in_mbuf->m_data;
/* read data from HSCX fifo */
HSCX_RDFIFO(h_chan, chan->in_cbptr, sc->sc_bfifolen);
chan->in_cbptr += sc->sc_bfifolen;
chan->in_len = sc->sc_bfifolen;
chan->rxcount += sc->sc_bfifolen;
}
else
{
NDBGL1(L1_H_XFRERR, "RAWHDLC rx buffer overflow in RPF, in_len=%d", chan->in_len);
chan->in_cbptr = chan->in_mbuf->m_data;
chan->in_len = 0;
cmd |= (HSCX_CMDR_RHR);
}
}
/* command to release fifo space */
cmd |= HSCX_CMDR_RMC;
}
/* transmit fifo empty, new data can be written to fifo */
if(ista & HSCX_ISTA_XPR)
{
/*
* for a description what is going on here, please have
* a look at isic_bchannel_start() in i4b_bchan.c !
*/
int activity = -1;
int len;
int nextlen;
NDBGL1(L1_H_IRQ, "unit %d, chan %d - XPR, Tx Fifo Empty!", sc->sc_unit, h_chan);
if(chan->out_mbuf_cur == NULL) /* last frame is transmitted */
{
IF_DEQUEUE(&chan->tx_queue, chan->out_mbuf_head);
if(chan->out_mbuf_head == NULL)
{
chan->state &= ~HSCX_TX_ACTIVE;
(*chan->drvr_linktab->bch_tx_queue_empty)(chan->drvr_linktab->unit);
}
else
{
chan->state |= HSCX_TX_ACTIVE;
chan->out_mbuf_cur = chan->out_mbuf_head;
chan->out_mbuf_cur_ptr = chan->out_mbuf_cur->m_data;
chan->out_mbuf_cur_len = chan->out_mbuf_cur->m_len;
if(sc->sc_trace & TRACE_B_TX)
{
i4b_trace_hdr hdr;
hdr.type = (h_chan == HSCX_CH_A ? TRC_CH_B1 : TRC_CH_B2);
hdr.dir = FROM_TE;
hdr.count = ++sc->sc_trace_bcount;
isdn_layer2_trace_ind(sc->sc_l2, &hdr, chan->out_mbuf_cur->m_len, chan->out_mbuf_cur->m_data);
}
if(chan->bprot == BPROT_NONE)
{
if(!(isic_hscx_silence(chan->out_mbuf_cur->m_data, chan->out_mbuf_cur->m_len)))
activity = ACT_TX;
}
else
{
activity = ACT_TX;
}
}
}
len = 0;
while(chan->out_mbuf_cur && len != sc->sc_bfifolen)
{
nextlen = min(chan->out_mbuf_cur_len, sc->sc_bfifolen - len);
#ifdef NOTDEF
printf("i:mh=%x, mc=%x, mcp=%x, mcl=%d l=%d nl=%d # ",
chan->out_mbuf_head,
chan->out_mbuf_cur,
chan->out_mbuf_cur_ptr,
chan->out_mbuf_cur_len,
len,
next_len);
#endif
isic_hscx_waitxfw(sc, h_chan); /* necessary !!! */
HSCX_WRFIFO(h_chan, chan->out_mbuf_cur_ptr, nextlen);
cmd |= HSCX_CMDR_XTF;
len += nextlen;
chan->txcount += nextlen;
chan->out_mbuf_cur_ptr += nextlen;
chan->out_mbuf_cur_len -= nextlen;
if(chan->out_mbuf_cur_len == 0)
{
if((chan->out_mbuf_cur = chan->out_mbuf_cur->m_next) != NULL)
{
chan->out_mbuf_cur_ptr = chan->out_mbuf_cur->m_data;
chan->out_mbuf_cur_len = chan->out_mbuf_cur->m_len;
if(sc->sc_trace & TRACE_B_TX)
{
i4b_trace_hdr hdr;
hdr.type = (h_chan == HSCX_CH_A ? TRC_CH_B1 : TRC_CH_B2);
hdr.dir = FROM_TE;
hdr.count = ++sc->sc_trace_bcount;
isdn_layer2_trace_ind(sc->sc_l2, &hdr, chan->out_mbuf_cur->m_len, chan->out_mbuf_cur->m_data);
}
}
else
{
if (chan->bprot != BPROT_NONE)
cmd |= HSCX_CMDR_XME;
i4b_Bfreembuf(chan->out_mbuf_head);
chan->out_mbuf_head = NULL;
}
}
}
}
if(cmd) /* is there a command for the HSCX ? */
{
isic_hscx_cmd(sc, h_chan, cmd); /* yes, to HSCX */
}
/* call timeout handling routine */
if(activity == ACT_RX || activity == ACT_TX)
(*chan->drvr_linktab->bch_activity)(chan->drvr_linktab->unit, activity);
}
/*---------------------------------------------------------------------------*
* HSCX initialization
*
* for telephony: extended transparent mode 1
* for raw hdlc: transparent mode 0
*---------------------------------------------------------------------------*/
void
isic_hscx_init(struct l1_softc *sc, int h_chan, int activate)
{
l1_bchan_state_t *chan = &sc->sc_chan[h_chan];
HSCX_WRITE(h_chan, H_MASK, 0xff); /* mask irq's */
if(sc->sc_ipac)
{
/* CCR1: Power Up, Clock Mode 5 */
HSCX_WRITE(h_chan, H_CCR1, HSCX_CCR1_PU | /* power up */
HSCX_CCR1_CM1); /* IPAC clock mode 5 */
}
else
{
/* CCR1: Power Up, Clock Mode 5 */
HSCX_WRITE(h_chan, H_CCR1, HSCX_CCR1_PU | /* power up */
HSCX_CCR1_CM2 | /* HSCX clock mode 5 */
HSCX_CCR1_CM0);
}
/* XAD1: Transmit Address Byte 1 */
HSCX_WRITE(h_chan, H_XAD1, 0xff);
/* XAD2: Transmit Address Byte 2 */
HSCX_WRITE(h_chan, H_XAD2, 0xff);
/* RAH2: Receive Address Byte High Reg. 2 */
HSCX_WRITE(h_chan, H_RAH2, 0xff);
/* XBCH: reset Transmit Byte Count High */
HSCX_WRITE(h_chan, H_XBCH, 0x00);
/* RLCR: reset Receive Length Check Register */
HSCX_WRITE(h_chan, H_RLCR, 0x00);
/* CCR2: set tx/rx clock shift bit 0 */
/* disable CTS irq, disable RIE irq*/
HSCX_WRITE(h_chan, H_CCR2, HSCX_CCR2_XCS0|HSCX_CCR2_RCS0);
/* XCCR: tx bit count per time slot */
HSCX_WRITE(h_chan, H_XCCR, 0x07);
/* RCCR: rx bit count per time slot */
HSCX_WRITE(h_chan, H_RCCR, 0x07);
if(sc->sc_bustyp == BUS_TYPE_IOM2)
{
switch(h_chan)
{
case HSCX_CH_A: /* Prepare HSCX channel A */
/* TSAX: tx clock shift bits 1 & 2 */
/* tx time slot number */
HSCX_WRITE(h_chan, H_TSAX, 0x2f);
/* TSAR: rx clock shift bits 1 & 2 */
/* rx time slot number */
HSCX_WRITE(h_chan, H_TSAR, 0x2f);
break;
case HSCX_CH_B: /* Prepare HSCX channel B */
/* TSAX: tx clock shift bits 1 & 2 */
/* tx time slot number */
HSCX_WRITE(h_chan, H_TSAX, 0x03);
/* TSAR: rx clock shift bits 1 & 2 */
/* rx time slot number */
HSCX_WRITE(h_chan, H_TSAR, 0x03);
break;
}
}
else /* IOM 1 setup */
{
/* TSAX: tx clock shift bits 1 & 2 */
/* tx time slot number */
HSCX_WRITE(h_chan, H_TSAX, 0x07);
/* TSAR: rx clock shift bits 1 & 2 */
/* rx time slot number */
HSCX_WRITE(h_chan, H_TSAR, 0x07);
}
if(activate)
{
if(chan->bprot == BPROT_RHDLC)
{
/* HDLC Frames, transparent mode 0 */
HSCX_WRITE(h_chan, H_MODE,
HSCX_MODE_MDS1|HSCX_MODE_RAC|HSCX_MODE_RTS);
}
else
{
/* Raw Telephony, extended transparent mode 1 */
HSCX_WRITE(h_chan, H_MODE,
HSCX_MODE_MDS1|HSCX_MODE_MDS0|HSCX_MODE_ADM|HSCX_MODE_RTS);
}
isic_hscx_cmd(sc, h_chan, HSCX_CMDR_RHR|HSCX_CMDR_XRES);
}
else
{
/* TSAX: tx time slot */
HSCX_WRITE(h_chan, H_TSAX, 0xff);
/* TSAR: rx time slot */
HSCX_WRITE(h_chan, H_TSAR, 0xff);
/* Raw Telephony, extended transparent mode 1 */
HSCX_WRITE(h_chan, H_MODE,
HSCX_MODE_MDS1|HSCX_MODE_MDS0|HSCX_MODE_ADM|HSCX_MODE_RTS);
}
/* don't touch ICA, EXA and EXB bits, this could be HSCX_CH_B */
/* always disable RSC and TIN */
chan->hscx_mask |= HSCX_MASK_RSC | HSCX_MASK_TIN;
if(activate)
{
/* enable */
chan->hscx_mask &= ~(HSCX_MASK_RME | HSCX_MASK_RPF | HSCX_MASK_XPR);
}
else
{
/* disable */
chan->hscx_mask |= HSCX_MASK_RME | HSCX_MASK_RPF | HSCX_MASK_XPR;
}
/* handle ICA, EXA, and EXB via interrupt mask of channel b */
if (h_chan == HSCX_CH_A)
{
if (activate)
HSCX_B_IMASK &= ~(HSCX_MASK_EXA | HSCX_MASK_ICA);
else
HSCX_B_IMASK |= HSCX_MASK_EXA | HSCX_MASK_ICA;
HSCX_WRITE(HSCX_CH_A, H_MASK, HSCX_A_IMASK);
HSCX_WRITE(HSCX_CH_B, H_MASK, HSCX_B_IMASK);
}
else
{
if (activate)
HSCX_B_IMASK &= ~HSCX_MASK_EXB;
else
HSCX_B_IMASK |= HSCX_MASK_EXB;
HSCX_WRITE(HSCX_CH_B, H_MASK, HSCX_B_IMASK);
}
/* clear spurious interrupts left over */
if(h_chan == HSCX_CH_A)
{
HSCX_READ(h_chan, H_EXIR);
HSCX_READ(h_chan, H_ISTA);
}
else /* mask ICA, because it must not be cleared by reading ISTA */
{
HSCX_WRITE(HSCX_CH_B, H_MASK, HSCX_B_IMASK | HSCX_MASK_ICA);
HSCX_READ(h_chan, H_EXIR);
HSCX_READ(h_chan, H_ISTA);
HSCX_WRITE(HSCX_CH_B, H_MASK, HSCX_B_IMASK);
}
}
/*---------------------------------------------------------------------------*
* write command to HSCX command register
*---------------------------------------------------------------------------*/
void
isic_hscx_cmd(struct l1_softc *sc, int h_chan, unsigned char cmd)
{
int timeout = 20;
while(((HSCX_READ(h_chan, H_STAR)) & HSCX_STAR_CEC) && timeout)
{
DELAY(10);
timeout--;
}
if(timeout == 0)
{
NDBGL1(L1_H_ERR, "HSCX wait for CEC timeout!");
}
HSCX_WRITE(h_chan, H_CMDR, cmd);
}
/*---------------------------------------------------------------------------*
* wait for HSCX transmit FIFO write enable
*---------------------------------------------------------------------------*/
void
isic_hscx_waitxfw(struct l1_softc *sc, int h_chan)
{
#define WAITVAL 50
#define WAITTO 200
int timeout = WAITTO;
while((!(((HSCX_READ(h_chan, H_STAR)) &
(HSCX_STAR_CEC | HSCX_STAR_XFW)) == HSCX_STAR_XFW)) && timeout)
{
DELAY(WAITVAL);
timeout--;
}
if(timeout == 0)
{
NDBGL1(L1_H_ERR, "HSCX wait for XFW timeout!");
}
else if (timeout != WAITTO)
{
NDBGL1(L1_H_XFRERR, "HSCX wait for XFW time: %d uS", (WAITTO-timeout)*50);
}
}