/*-
* Copyright (c) 1998 Iain Hibbert
* 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 ``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 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.
*/
/*
* Driver for Magma SBus Serial/Parallel cards using the Cirrus Logic
* CD1400 & CD1190 chips
*/
/************************************************************************
*
* CD1400 Routines
*
* cd1400_compute_baud calculate COR/BPR register values
* cd1400_write_ccr write a value to CD1400 ccr
* cd1400_read_reg read from a CD1400 register
* cd1400_write_reg write to a CD1400 register
* cd1400_enable_transmitter enable transmitting on CD1400 channel
*/
/*
* compute the bpr/cor pair for any baud rate
* returns 0 for success, 1 for failure
*/
int
cd1400_compute_baud(speed_t speed, int clock, int *cor, int *bpr)
{
int c, co, br;
if( speed < 50 || speed > 150000 )
return(1);
for( c = 0, co = 8 ; co <= 2048 ; co <<= 2, c++ ) {
br = ((clock * 1000000) + (co * speed) / 2) / (co * speed);
if( br < 0x100 ) {
*bpr = br;
*cor = c;
return(0);
}
}
return(1);
}
/*
* Write a CD1400 channel command, should have a timeout?
*/
inline void
cd1400_write_ccr(struct cd1400 *cd, u_char cmd)
{
while( cd1400_read_reg(cd, CD1400_CCR) )
;
cd1400_write_reg(cd, CD1400_CCR, cmd);
}
/*
* read a value from a cd1400 register
*/
inline u_char
cd1400_read_reg(struct cd1400 *cd, int reg)
{
return(cd->cd_reg[reg]);
}
/*
* write a value to a cd1400 register
*/
inline void
cd1400_write_reg(struct cd1400 *cd, int reg, u_char value)
{
cd->cd_reg[reg] = value;
}
/*
* enable transmit service requests for cd1400 channel
*/
void
cd1400_enable_transmitter(struct cd1400 *cd, int channel)
{
int s, srer;
/************************************************************************
*
* Magma Routines
*
* magma_match reports if we have a magma board available
* magma_attach attaches magma boards to the sbus
* magma_hard hardware level interrupt routine
* magma_soft software level interrupt routine
*/
/* See if we support this device */
for (card = supported_cards; ; card++) {
if (card->mb_sbusname == NULL)
/* End of table: no match */
return (0);
if (strcmp(sa->sa_name, card->mb_sbusname) == 0)
break;
}
/*
* Find the card model.
* Older models all have sbus node name `MAGMA_Sp' (see
* `supported_cards[]' above), and must be distinguished
* by the `magma_prom' property.
*/
magma_prom = prom_getpropstring(node, "magma_prom");
for (card = supported_cards; card->mb_name != NULL; card++) {
if (strcmp(sa->sa_name, card->mb_sbusname) != 0)
/* Sbus node name doesn't match */
continue;
if (strcmp(magma_prom, card->mb_name) == 0)
/* Model name match */
break;
}
/* prom_getpropstring(node, "chiprev"); */
/* seemingly the Magma drivers just ignore the propstring */
cd->cd_chiprev = cd1400_read_reg(cd, CD1400_GFRCR);
/* wait for revision code to be restored */
while( cd1400_read_reg(cd, CD1400_GFRCR) != cd->cd_chiprev )
;
/* set the Prescaler Period Register to tick at 1ms */
cd1400_write_reg(cd, CD1400_PPR,
((cd->cd_clock * 1000000 / CD1400_PPR_PRESCALER + 500) / 1000));
/* The LC2+1Sp card is the only card that doesn't have
* a CD1190 for the parallel port, but uses channel 0 of
* the CD1400, so we make a note of it for later and set up
* the CD1400 for parallel mode operation.
*/
if( card->mb_npar && card->mb_ncd1190 == 0 ) {
cd1400_write_reg(cd, CD1400_GCR, CD1400_GCR_PARALLEL);
cd->cd_parmode = 1;
}
}
/*
* hard interrupt routine
*
* returns 1 if it handled it, otherwise 0
*
* runs at IPL_SERIAL
*/
int
magma_hard(void *arg)
{
struct magma_softc *sc = arg;
struct cd1400 *cd;
int chip, status = 0;
int serviced = 0;
int needsoftint = 0;
/*
* check status of all the CD1400 chips
*/
for( chip = 0 ; chip < sc->ms_ncd1400 ; chip++ )
status |= cd1400_read_reg(&sc->ms_cd1400[chip], CD1400_SVRR);
if( ISSET(status, CD1400_SVRR_RXRDY) ) {
u_char rivr = *sc->ms_svcackr; /* enter rx service context */
int port = rivr >> 4;
if( rivr & (1<<3) ) { /* parallel port */
struct mbpp_port *mbpp;
int n_chars;
mbpp = &sc->ms_mbpp->ms_port[port];
cd = mbpp->mp_cd1400;
/* don't think we have to handle exceptions */
n_chars = cd1400_read_reg(cd, CD1400_RDCR);
while (n_chars--) {
if( mbpp->mp_cnt == 0 ) {
SET(mbpp->mp_flags, MBPPF_WAKEUP);
needsoftint = 1;
break;
}
*mbpp->mp_ptr = cd1400_read_reg(cd,CD1400_RDSR);
mbpp->mp_ptr++;
mbpp->mp_cnt--;
}
} else { /* serial port */
struct mtty_port *mtty;
u_char *ptr, n_chars, line_stat;
mtty = &sc->ms_mtty->ms_port[port];
cd = mtty->mp_cd1400;
if( ISSET(rivr, CD1400_RIVR_EXCEPTION) ) {
line_stat = cd1400_read_reg(cd, CD1400_RDSR);
n_chars = 1;
} else { /* no exception, received data OK */
line_stat = 0;
n_chars = cd1400_read_reg(cd, CD1400_RDCR);
}
mbpp = &sc->ms_mbpp->ms_port[port];
cd = mbpp->mp_cd1400;
if( mbpp->mp_cnt ) {
int count = 0;
/* fill the fifo */
while (mbpp->mp_cnt &&
count++ < CD1400_PAR_FIFO_SIZE) {
cd1400_write_reg(cd, CD1400_TDR,
*mbpp->mp_ptr);
mbpp->mp_ptr++;
mbpp->mp_cnt--;
}
} else {
/*
* fifo is empty and we got no more data
* to send, so shut off interrupts and
* signal for a wakeup, which can't be
* done here in case we beat mbpp_send to
* the tsleep call (we are running at >spltty)
*/
cd1400_write_reg(cd, CD1400_SRER, 0);
SET(mbpp->mp_flags, MBPPF_WAKEUP);
needsoftint = 1;
}
} else { /* serial port */
struct mtty_port *mtty;
mtty = &sc->ms_mtty->ms_port[port];
cd = mtty->mp_cd1400;
if( !ISSET(mtty->mp_flags, MTTYF_STOP) ) {
int count = 0;
/* check if we should start/stop a break */
if( ISSET(mtty->mp_flags, MTTYF_SET_BREAK) ) {
cd1400_write_reg(cd, CD1400_TDR, 0);
cd1400_write_reg(cd, CD1400_TDR, 0x81);
/* should we delay too? */
CLR(mtty->mp_flags, MTTYF_SET_BREAK);
count += 2;
}
/* I don't quite fill the fifo in case the last one is a
* NULL which I have to double up because its the escape
* code for embedded transmit characters.
*/
while( mtty->mp_txc > 0 && count < CD1400_TX_FIFO_SIZE - 1 ) {
u_char ch;
/* if we ran out of work or are requested to STOP then
* shut off the txrdy interrupts and signal DONE to flush
* out the chars we have sent.
*/
if( mtty->mp_txc == 0 || ISSET(mtty->mp_flags, MTTYF_STOP) ) {
register int srer;
/*
* open routine. returns zero if successful, else error code
*/
int
mttyopen(dev_t dev, int flags, int mode, struct lwp *l)
{
int card = MAGMA_CARD(dev);
int port = MAGMA_PORT(dev);
struct mtty_softc *ms;
struct mtty_port *mp;
struct tty *tp;
struct cd1400 *cd;
int error, s;
if ((ms = device_lookup_private(&mtty_cd, card)) == NULL
|| port >= ms->ms_nports )
return(ENXIO); /* device not configured */
error = (*tp->t_linesw->l_open)(dev, tp);
if (error != 0)
goto bad;
bad:
if (!ISSET(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0) {
/*
* We failed to open the device, and nobody else had it opened.
* Clean up the state as appropriate.
*/
/* XXX - do that here */
}
return (error);
}
/*
* close routine. returns zero if successful, else error code
*/
int
mttyclose(dev_t dev, int flag, int mode, struct lwp *l)
{
struct mtty_softc *ms = device_lookup_private(&mtty_cd,
MAGMA_CARD(dev));
struct mtty_port *mp = &ms->ms_port[MAGMA_PORT(dev)];
struct tty *tp = mp->mp_tty;
int s;
(*tp->t_linesw->l_close)(tp, flag);
ttyclose(tp);
s = spltty();
/* if HUPCL is set, and the tty is no longer open
* shut down the port
*/
if( ISSET(tp->t_cflag, HUPCL) || !ISSET(tp->t_state, TS_ISOPEN) ) {
/* XXX wait until FIFO is empty before turning off the channel
struct cd1400 *cd = mp->mp_cd1400;
*/
/* drop DTR and RTS */
(void)mtty_modem_control(mp, 0, DMSET);
/* turn off the channel
cd1400_write_reg(cd, CD1400_CAR, mp->mp_channel);
cd1400_write_ccr(cd, CD1400_CCR_CMDRESET);
*/
}
/*
* the transmit interrupt routine will disable transmit when it
* notices that MTTYF_STOP has been set.
*/
SET(mp->mp_flags, MTTYF_STOP);
}
splx(s);
}
/*
* Start output, after a stop.
*/
void
mtty_start(struct tty *tp)
{
struct mtty_softc *ms = device_lookup_private(&mtty_cd,
MAGMA_CARD(tp->t_dev));
struct mtty_port *mp = &ms->ms_port[MAGMA_PORT(tp->t_dev)];
int s;
s = spltty();
/* we only need to do something if we are not already busy
* or delaying or stopped
*/
if( !ISSET(tp->t_state, TS_TTSTOP | TS_TIMEOUT | TS_BUSY) ) {
if (ttypull(tp)) {
mp->mp_txc = ndqb(&tp->t_outq, 0);
mp->mp_txp = tp->t_outq.c_cf;
SET(tp->t_state, TS_BUSY);
cd1400_enable_transmitter(mp->mp_cd1400, mp->mp_channel);
}
}
splx(s);
}
/*
* set/get modem line status
*
* bits can be: TIOCM_DTR, TIOCM_RTS, TIOCM_CTS, TIOCM_CD, TIOCM_RI, TIOCM_DSR
*
* note that DTR and RTS lines are exchanged, and that DSR is
* not available on the LC2+1Sp card (used as CD)
*
* only let them fiddle with RTS if CRTSCTS is not enabled
*/
int
mtty_modem_control(struct mtty_port *mp, int bits, int howto)
{
struct cd1400 *cd = mp->mp_cd1400;
struct tty *tp = mp->mp_tty;
int s, msvr;
s = spltty();
cd1400_write_reg(cd, CD1400_CAR, mp->mp_channel);
switch(howto) {
case DMGET: /* get bits */
bits = 0;
/*
* open routine. returns zero if successful, else error code
*/
int
mbppopen(dev_t dev, int flags, int mode, struct lwp *l)
{
int card = MAGMA_CARD(dev);
int port = MAGMA_PORT(dev);
struct mbpp_softc *ms;
struct mbpp_port *mp;
int s;
if ((ms = device_lookup_private(&mbpp_cd, card)) == NULL
|| port >= ms->ms_nports )
return(ENXIO);
/*
* don't call uiomove again until we used all the data we grabbed
*/
if( uio->uio_rw == UIO_WRITE && cnt != len ) {
ptr += cnt;
len -= cnt;
cnt = 0;
goto again;
}
}
/*
* adjust for those chars that we uiomoved but never actually wrote
*/
if( uio->uio_rw == UIO_WRITE && cnt != len ) {
uio->uio_resid += (len - cnt);
}