54f42f17fc
to become idle; it actually doesn't matter as the chip takes care of this for us. It would also spin endlessly in certain circumstances during shutdown. While I'm here, (finally) add code to allow sending BREAK characters.
1622 lines
39 KiB
C
1622 lines
39 KiB
C
/* $NetBSD: clmpcc.c,v 1.9 1999/11/28 12:23:18 scw Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 1999 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Steve C. Woodford.
|
|
*
|
|
* 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.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* Cirrus Logic CD2400/CD2401 Four Channel Multi-Protocol Comms. Controller.
|
|
*/
|
|
|
|
#include "opt_ddb.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/tty.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/user.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/file.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/device.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/param.h>
|
|
|
|
#include <dev/ic/clmpccreg.h>
|
|
#include <dev/ic/clmpccvar.h>
|
|
#include <dev/cons.h>
|
|
|
|
|
|
#if defined(CLMPCC_ONLY_BYTESWAP_LOW) && defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
#error "CLMPCC_ONLY_BYTESWAP_LOW and CLMPCC_ONLY_BYTESWAP_HIGH are mutually exclusive."
|
|
#endif
|
|
|
|
|
|
static int clmpcc_init __P((struct clmpcc_softc *sc));
|
|
static void clmpcc_shutdown __P((struct clmpcc_chan *));
|
|
static int clmpcc_speed __P((struct clmpcc_softc *, speed_t,
|
|
int *, int *));
|
|
static int clmpcc_param __P((struct tty *, struct termios *));
|
|
static void clmpcc_set_params __P((struct clmpcc_chan *));
|
|
static void clmpcc_start __P((struct tty *));
|
|
static int clmpcc_modem_control __P((struct clmpcc_chan *, int, int));
|
|
|
|
|
|
cdev_decl(clmpcc);
|
|
|
|
#define CLMPCCUNIT(x) (minor(x) & 0x7fffc)
|
|
#define CLMPCCCHAN(x) (minor(x) & 0x00003)
|
|
#define CLMPCCDIALOUT(x) (minor(x) & 0x80000)
|
|
|
|
/*
|
|
* These should be in a header file somewhere...
|
|
*/
|
|
#define ISSET(v, f) (((v) & (f)) != 0)
|
|
#define ISCLR(v, f) (((v) & (f)) == 0)
|
|
#define SET(v, f) (v) |= (f)
|
|
#define CLR(v, f) (v) &= ~(f)
|
|
|
|
|
|
extern struct cfdriver clmpcc_cd;
|
|
|
|
|
|
/*
|
|
* Make this an option variable one can patch.
|
|
*/
|
|
u_int clmpcc_ibuf_size = CLMPCC_RING_SIZE;
|
|
|
|
|
|
/*
|
|
* Things needed when the device is used as a console
|
|
*/
|
|
static struct clmpcc_softc *cons_sc = NULL;
|
|
static int cons_chan;
|
|
static int cons_rate;
|
|
|
|
static int clmpcc_common_getc __P((struct clmpcc_softc *, int));
|
|
static void clmpcc_common_putc __P((struct clmpcc_softc *, int, int));
|
|
int clmpcccngetc __P((dev_t));
|
|
void clmpcccnputc __P((dev_t, int));
|
|
|
|
|
|
/*
|
|
* Convenience functions, inlined for speed
|
|
*/
|
|
#define integrate static inline
|
|
integrate u_int8_t clmpcc_rdreg __P((struct clmpcc_softc *, u_int));
|
|
integrate void clmpcc_wrreg __P((struct clmpcc_softc *, u_int, u_int));
|
|
integrate u_int8_t clmpcc_rdreg_odd __P((struct clmpcc_softc *, u_int));
|
|
integrate void clmpcc_wrreg_odd __P((struct clmpcc_softc *, u_int, u_int));
|
|
integrate void clmpcc_wrtx_multi __P((struct clmpcc_softc *, u_int8_t *,
|
|
u_int));
|
|
integrate u_int8_t clmpcc_select_channel __P((struct clmpcc_softc *, u_int));
|
|
integrate void clmpcc_channel_cmd __P((struct clmpcc_softc *,int,int));
|
|
integrate void clmpcc_enable_transmitter __P((struct clmpcc_chan *));
|
|
|
|
#define clmpcc_rd_msvr(s) clmpcc_rdreg_odd(s,CLMPCC_REG_MSVR)
|
|
#define clmpcc_wr_msvr(s,r,v) clmpcc_wrreg_odd(s,r,v)
|
|
#define clmpcc_wr_pilr(s,r,v) clmpcc_wrreg_odd(s,r,v)
|
|
#define clmpcc_rd_rxdata(s) clmpcc_rdreg_odd(s,CLMPCC_REG_RDR)
|
|
#define clmpcc_wr_txdata(s,v) clmpcc_wrreg_odd(s,CLMPCC_REG_TDR,v)
|
|
|
|
|
|
integrate u_int8_t
|
|
clmpcc_rdreg(sc, offset)
|
|
struct clmpcc_softc *sc;
|
|
u_int offset;
|
|
{
|
|
#if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= sc->sc_byteswap;
|
|
#elif defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= CLMPCC_BYTESWAP_HIGH;
|
|
#endif
|
|
return bus_space_read_1(sc->sc_iot, sc->sc_ioh, offset);
|
|
}
|
|
|
|
integrate void
|
|
clmpcc_wrreg(sc, offset, val)
|
|
struct clmpcc_softc *sc;
|
|
u_int offset;
|
|
u_int val;
|
|
{
|
|
#if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= sc->sc_byteswap;
|
|
#elif defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= CLMPCC_BYTESWAP_HIGH;
|
|
#endif
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, offset, val);
|
|
}
|
|
|
|
integrate u_int8_t
|
|
clmpcc_rdreg_odd(sc, offset)
|
|
struct clmpcc_softc *sc;
|
|
u_int offset;
|
|
{
|
|
#if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (sc->sc_byteswap & 2);
|
|
#elif defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (CLMPCC_BYTESWAP_HIGH & 2);
|
|
#endif
|
|
return bus_space_read_1(sc->sc_iot, sc->sc_ioh, offset);
|
|
}
|
|
|
|
integrate void
|
|
clmpcc_wrreg_odd(sc, offset, val)
|
|
struct clmpcc_softc *sc;
|
|
u_int offset;
|
|
u_int val;
|
|
{
|
|
#if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (sc->sc_byteswap & 2);
|
|
#elif defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (CLMPCC_BYTESWAP_HIGH & 2);
|
|
#endif
|
|
bus_space_write_1(sc->sc_iot, sc->sc_ioh, offset, val);
|
|
}
|
|
|
|
integrate void
|
|
clmpcc_wrtx_multi(sc, buff, count)
|
|
struct clmpcc_softc *sc;
|
|
u_int8_t *buff;
|
|
u_int count;
|
|
{
|
|
u_int offset = CLMPCC_REG_TDR;
|
|
|
|
#if !defined(CLMPCC_ONLY_BYTESWAP_LOW) && !defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (sc->sc_byteswap & 2);
|
|
#elif defined(CLMPCC_ONLY_BYTESWAP_HIGH)
|
|
offset ^= (CLMPCC_BYTESWAP_HIGH & 2);
|
|
#endif
|
|
bus_space_write_multi_1(sc->sc_iot, sc->sc_ioh, offset, buff, count);
|
|
}
|
|
|
|
integrate u_int8_t
|
|
clmpcc_select_channel(sc, new_chan)
|
|
struct clmpcc_softc *sc;
|
|
u_int new_chan;
|
|
{
|
|
u_int old_chan = clmpcc_rdreg_odd(sc, CLMPCC_REG_CAR);
|
|
|
|
clmpcc_wrreg_odd(sc, CLMPCC_REG_CAR, new_chan);
|
|
|
|
return old_chan;
|
|
}
|
|
|
|
integrate void
|
|
clmpcc_channel_cmd(sc, chan, cmd)
|
|
struct clmpcc_softc *sc;
|
|
int chan;
|
|
int cmd;
|
|
{
|
|
int i;
|
|
|
|
for (i = 5000; i; i--) {
|
|
if ( clmpcc_rdreg(sc, CLMPCC_REG_CCR) == 0 )
|
|
break;
|
|
delay(1);
|
|
}
|
|
|
|
if ( i == 0 )
|
|
printf("%s: channel %d command timeout (idle)\n",
|
|
sc->sc_dev.dv_xname, chan);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_CCR, cmd);
|
|
}
|
|
|
|
integrate void
|
|
clmpcc_enable_transmitter(ch)
|
|
struct clmpcc_chan *ch;
|
|
{
|
|
u_int old;
|
|
int s;
|
|
|
|
old = clmpcc_select_channel(ch->ch_sc, ch->ch_car);
|
|
|
|
s = splserial();
|
|
clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER,
|
|
clmpcc_rdreg(ch->ch_sc, CLMPCC_REG_IER) | CLMPCC_IER_TX_EMPTY);
|
|
SET(ch->ch_tty->t_state, TS_BUSY);
|
|
splx(s);
|
|
|
|
clmpcc_select_channel(ch->ch_sc, old);
|
|
}
|
|
|
|
static int
|
|
clmpcc_speed(sc, speed, cor, bpr)
|
|
struct clmpcc_softc *sc;
|
|
speed_t speed;
|
|
int *cor, *bpr;
|
|
{
|
|
int c, co, br;
|
|
|
|
for (co = 0, c = 8; c <= 2048; co++, c *= 4) {
|
|
br = ((sc->sc_clk / c) / speed) - 1;
|
|
if ( br < 0x100 ) {
|
|
*cor = co;
|
|
*bpr = br;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void
|
|
clmpcc_attach(sc)
|
|
struct clmpcc_softc *sc;
|
|
{
|
|
struct clmpcc_chan *ch;
|
|
struct tty *tp;
|
|
int chan;
|
|
|
|
if ( cons_sc != NULL &&
|
|
sc->sc_iot == cons_sc->sc_iot && sc->sc_ioh == cons_sc->sc_ioh )
|
|
cons_sc = sc;
|
|
|
|
/* Initialise the chip */
|
|
clmpcc_init(sc);
|
|
|
|
printf(": Cirrus Logic CD240%c Serial Controller\n",
|
|
(clmpcc_rd_msvr(sc) & CLMPCC_MSVR_PORT_ID) ? '0' : '1');
|
|
|
|
sc->sc_soft_running = 0;
|
|
memset(&(sc->sc_chans[0]), 0, sizeof(sc->sc_chans));
|
|
|
|
for (chan = 0; chan < CLMPCC_NUM_CHANS; chan++) {
|
|
ch = &sc->sc_chans[chan];
|
|
|
|
ch->ch_sc = sc;
|
|
ch->ch_car = chan;
|
|
|
|
tp = ttymalloc();
|
|
tp->t_oproc = clmpcc_start;
|
|
tp->t_param = clmpcc_param;
|
|
|
|
ch->ch_tty = tp;
|
|
|
|
ch->ch_ibuf = malloc(clmpcc_ibuf_size * 2, M_DEVBUF, M_NOWAIT);
|
|
if ( ch->ch_ibuf == NULL ) {
|
|
printf("%s(%d): unable to allocate ring buffer\n",
|
|
sc->sc_dev.dv_xname, chan);
|
|
return;
|
|
}
|
|
|
|
ch->ch_ibuf_end = &(ch->ch_ibuf[clmpcc_ibuf_size * 2]);
|
|
ch->ch_ibuf_rd = ch->ch_ibuf_wr = ch->ch_ibuf;
|
|
|
|
tty_attach(tp);
|
|
}
|
|
|
|
printf("%s: %d channels available", sc->sc_dev.dv_xname,
|
|
CLMPCC_NUM_CHANS);
|
|
if ( cons_sc == sc ) {
|
|
printf(", console on channel %d.\n", cons_chan);
|
|
SET(sc->sc_chans[cons_chan].ch_flags, CLMPCC_FLG_IS_CONSOLE);
|
|
SET(sc->sc_chans[cons_chan].ch_openflags, TIOCFLAG_SOFTCAR);
|
|
} else
|
|
printf(".\n");
|
|
}
|
|
|
|
static int
|
|
clmpcc_init(sc)
|
|
struct clmpcc_softc *sc;
|
|
{
|
|
u_int tcor, tbpr;
|
|
u_int rcor, rbpr;
|
|
u_int msvr_rts, msvr_dtr;
|
|
u_int ccr;
|
|
int is_console;
|
|
int i;
|
|
|
|
/*
|
|
* All we're really concerned about here is putting the chip
|
|
* into a quiescent state so that it won't do anything until
|
|
* clmpccopen() is called. (Except the console channel.)
|
|
*/
|
|
|
|
/*
|
|
* If the chip is acting as console, set all channels to the supplied
|
|
* console baud rate. Otherwise, plump for 9600.
|
|
*/
|
|
if ( cons_sc &&
|
|
sc->sc_ioh == cons_sc->sc_ioh && sc->sc_iot == cons_sc->sc_iot ) {
|
|
clmpcc_speed(sc, cons_rate, &tcor, &tbpr);
|
|
clmpcc_speed(sc, cons_rate, &rcor, &rbpr);
|
|
is_console = 1;
|
|
} else {
|
|
clmpcc_speed(sc, 9600, &tcor, &tbpr);
|
|
clmpcc_speed(sc, 9600, &rcor, &rbpr);
|
|
is_console = 0;
|
|
}
|
|
|
|
/* Allow any pending output to be sent */
|
|
delay(10000);
|
|
|
|
/* Send the Reset All command to channel 0 (resets all channels!) */
|
|
clmpcc_channel_cmd(sc, 0, CLMPCC_CCR_T0_RESET_ALL);
|
|
|
|
delay(1000);
|
|
|
|
/*
|
|
* The chip will set it's firmware revision register to a non-zero
|
|
* value to indicate completion of reset.
|
|
*/
|
|
for (i = 10000; clmpcc_rdreg(sc, CLMPCC_REG_GFRCR) == 0 && i; i--)
|
|
delay(1);
|
|
|
|
if ( i == 0 ) {
|
|
/*
|
|
* Watch out... If this chip is console, the message
|
|
* probably won't be sent since we just reset it!
|
|
*/
|
|
printf("%s: Failed to reset chip\n", sc->sc_dev.dv_xname);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < CLMPCC_NUM_CHANS; i++) {
|
|
clmpcc_select_channel(sc, i);
|
|
|
|
/* All interrupts are disabled to begin with */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, 0);
|
|
|
|
/* Make sure the channel interrupts on the correct vectors */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_LIVR, sc->sc_vector_base);
|
|
clmpcc_wr_pilr(sc, CLMPCC_REG_RPILR, sc->sc_rpilr);
|
|
clmpcc_wr_pilr(sc, CLMPCC_REG_TPILR, sc->sc_tpilr);
|
|
clmpcc_wr_pilr(sc, CLMPCC_REG_MPILR, sc->sc_mpilr);
|
|
|
|
/* Receive timer prescaler set to 1ms */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TPR,
|
|
CLMPCC_MSEC_TO_TPR(sc->sc_clk, 1));
|
|
|
|
/* We support Async mode only */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_CMR, CLMPCC_CMR_ASYNC);
|
|
|
|
/* Set the required baud rate */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TCOR, CLMPCC_TCOR_CLK(tcor));
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TBPR, tbpr);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RCOR, CLMPCC_RCOR_CLK(rcor));
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RBPR, rbpr);
|
|
|
|
/* Always default to 8N1 (XXX what about console?) */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR1, CLMPCC_COR1_CHAR_8BITS |
|
|
CLMPCC_COR1_NO_PARITY |
|
|
CLMPCC_COR1_IGNORE_PAR);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR2, 0);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR3, CLMPCC_COR3_STOP_1);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR4, CLMPCC_COR4_DSRzd |
|
|
CLMPCC_COR4_CDzd |
|
|
CLMPCC_COR4_CTSzd);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR5, CLMPCC_COR5_DSRod |
|
|
CLMPCC_COR5_CDod |
|
|
CLMPCC_COR5_CTSod |
|
|
CLMPCC_COR5_FLOW_NORM);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR6, 0);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR7, 0);
|
|
|
|
/* Set the receive FIFO timeout */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RTPRl, CLMPCC_RTPR_DEFAULT);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RTPRh, 0);
|
|
|
|
/* At this point, we set up the console differently */
|
|
if ( is_console && i == cons_chan ) {
|
|
msvr_rts = CLMPCC_MSVR_RTS;
|
|
msvr_dtr = CLMPCC_MSVR_DTR;
|
|
ccr = CLMPCC_CCR_T0_RX_EN | CLMPCC_CCR_T0_TX_EN;
|
|
} else {
|
|
msvr_rts = 0;
|
|
msvr_dtr = 0;
|
|
ccr = CLMPCC_CCR_T0_RX_DIS | CLMPCC_CCR_T0_TX_DIS;
|
|
}
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_MSVR_RTS, msvr_rts);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_MSVR_DTR, msvr_dtr);
|
|
clmpcc_channel_cmd(sc, i, CLMPCC_CCR_T0_INIT | ccr);
|
|
delay(100);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
clmpcc_shutdown(ch)
|
|
struct clmpcc_chan *ch;
|
|
{
|
|
int oldch;
|
|
|
|
oldch = clmpcc_select_channel(ch->ch_sc, ch->ch_car);
|
|
|
|
/* Turn off interrupts. */
|
|
clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER, 0);
|
|
|
|
if ( ISCLR(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) {
|
|
/* Disable the transmitter and receiver */
|
|
clmpcc_channel_cmd(ch->ch_sc, ch->ch_car, CLMPCC_CCR_T0_RX_DIS |
|
|
CLMPCC_CCR_T0_TX_DIS);
|
|
|
|
/* Drop RTS and DTR */
|
|
clmpcc_modem_control(ch, TIOCM_RTS | TIOCM_DTR, DMBIS);
|
|
}
|
|
|
|
clmpcc_select_channel(ch->ch_sc, oldch);
|
|
}
|
|
|
|
int
|
|
clmpccopen(dev, flag, mode, p)
|
|
dev_t dev;
|
|
int flag, mode;
|
|
struct proc *p;
|
|
{
|
|
struct clmpcc_softc *sc;
|
|
struct clmpcc_chan *ch;
|
|
struct tty *tp;
|
|
int oldch;
|
|
int error;
|
|
int unit;
|
|
|
|
if ( (unit = CLMPCCUNIT(dev)) >= clmpcc_cd.cd_ndevs ||
|
|
(sc = clmpcc_cd.cd_devs[unit]) == NULL ) {
|
|
return ENXIO;
|
|
}
|
|
|
|
ch = &sc->sc_chans[CLMPCCCHAN(dev)];
|
|
|
|
tp = ch->ch_tty;
|
|
|
|
if ( ISSET(tp->t_state, TS_ISOPEN) &&
|
|
ISSET(tp->t_state, TS_XCLUDE) && p->p_ucred->cr_uid != 0 )
|
|
return EBUSY;
|
|
|
|
/*
|
|
* Do the following iff this is a first open.
|
|
*/
|
|
if ( ISCLR(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 ) {
|
|
|
|
ttychars(tp);
|
|
|
|
tp->t_dev = dev;
|
|
tp->t_iflag = TTYDEF_IFLAG;
|
|
tp->t_oflag = TTYDEF_OFLAG;
|
|
tp->t_lflag = TTYDEF_LFLAG;
|
|
tp->t_cflag = TTYDEF_CFLAG;
|
|
tp->t_ospeed = tp->t_ispeed = TTYDEF_SPEED;
|
|
|
|
if ( ISSET(ch->ch_openflags, TIOCFLAG_CLOCAL) )
|
|
SET(tp->t_cflag, CLOCAL);
|
|
if ( ISSET(ch->ch_openflags, TIOCFLAG_CRTSCTS) )
|
|
SET(tp->t_cflag, CRTSCTS);
|
|
if ( ISSET(ch->ch_openflags, TIOCFLAG_MDMBUF) )
|
|
SET(tp->t_cflag, MDMBUF);
|
|
|
|
/*
|
|
* Override some settings if the channel is being
|
|
* used as the console.
|
|
*/
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) {
|
|
tp->t_ospeed = tp->t_ispeed = cons_rate;
|
|
SET(tp->t_cflag, CLOCAL);
|
|
CLR(tp->t_cflag, CRTSCTS);
|
|
CLR(tp->t_cflag, HUPCL);
|
|
}
|
|
|
|
ch->ch_control = 0;
|
|
|
|
clmpcc_param(tp, &tp->t_termios);
|
|
ttsetwater(tp);
|
|
|
|
/* Clear the input ring */
|
|
ch->ch_ibuf_rd = ch->ch_ibuf_wr = ch->ch_ibuf;
|
|
|
|
/* Select the channel */
|
|
oldch = clmpcc_select_channel(sc, ch->ch_car);
|
|
|
|
/* Reset it */
|
|
clmpcc_channel_cmd(sc, ch->ch_car, CLMPCC_CCR_T0_CLEAR |
|
|
CLMPCC_CCR_T0_RX_EN |
|
|
CLMPCC_CCR_T0_TX_EN);
|
|
|
|
/* Enable receiver and modem change interrupts. */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, CLMPCC_IER_MODEM |
|
|
CLMPCC_IER_RET |
|
|
CLMPCC_IER_RX_FIFO);
|
|
|
|
/* Raise RTS and DTR */
|
|
clmpcc_modem_control(ch, TIOCM_RTS | TIOCM_DTR, DMBIS);
|
|
|
|
clmpcc_select_channel(sc, oldch);
|
|
} else
|
|
if ( ISSET(tp->t_state, TS_XCLUDE) && p->p_ucred->cr_uid != 0 )
|
|
return EBUSY;
|
|
|
|
error = ttyopen(tp, CLMPCCDIALOUT(dev), ISSET(flag, O_NONBLOCK));
|
|
if (error)
|
|
goto bad;
|
|
|
|
error = (*linesw[tp->t_line].l_open)(dev, tp);
|
|
if (error)
|
|
goto bad;
|
|
|
|
return 0;
|
|
|
|
bad:
|
|
if ( ISCLR(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.
|
|
*/
|
|
clmpcc_shutdown(ch);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
clmpccclose(dev, flag, mode, p)
|
|
dev_t dev;
|
|
int flag, mode;
|
|
struct proc *p;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(dev)];
|
|
struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(dev)];
|
|
struct tty *tp = ch->ch_tty;
|
|
int s;
|
|
|
|
if ( ISCLR(tp->t_state, TS_ISOPEN) )
|
|
return 0;
|
|
|
|
(*linesw[tp->t_line].l_close)(tp, flag);
|
|
|
|
s = spltty();
|
|
|
|
if ( ISCLR(tp->t_state, TS_ISOPEN) && tp->t_wopen == 0 ) {
|
|
/*
|
|
* Although we got a last close, the device may still be in
|
|
* use; e.g. if this was the dialout node, and there are still
|
|
* processes waiting for carrier on the non-dialout node.
|
|
*/
|
|
clmpcc_shutdown(ch);
|
|
}
|
|
|
|
ttyclose(tp);
|
|
|
|
splx(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
clmpccread(dev, uio, flag)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
int flag;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(dev)];
|
|
struct tty *tp = sc->sc_chans[CLMPCCCHAN(dev)].ch_tty;
|
|
|
|
return ((*linesw[tp->t_line].l_read)(tp, uio, flag));
|
|
}
|
|
|
|
int
|
|
clmpccwrite(dev, uio, flag)
|
|
dev_t dev;
|
|
struct uio *uio;
|
|
int flag;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(dev)];
|
|
struct tty *tp = sc->sc_chans[CLMPCCCHAN(dev)].ch_tty;
|
|
|
|
return ((*linesw[tp->t_line].l_write)(tp, uio, flag));
|
|
}
|
|
|
|
struct tty *
|
|
clmpcctty(dev)
|
|
dev_t dev;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(dev)];
|
|
|
|
return (sc->sc_chans[CLMPCCCHAN(dev)].ch_tty);
|
|
}
|
|
|
|
int
|
|
clmpccioctl(dev, cmd, data, flag, p)
|
|
dev_t dev;
|
|
u_long cmd;
|
|
caddr_t data;
|
|
int flag;
|
|
struct proc *p;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(dev)];
|
|
struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(dev)];
|
|
struct tty *tp = ch->ch_tty;
|
|
int error;
|
|
|
|
error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, p);
|
|
if (error >= 0)
|
|
return error;
|
|
|
|
error = ttioctl(tp, cmd, data, flag, p);
|
|
if (error >= 0)
|
|
return error;
|
|
|
|
error = 0;
|
|
|
|
switch (cmd) {
|
|
case TIOCSBRK:
|
|
SET(ch->ch_flags, CLMPCC_FLG_START_BREAK);
|
|
clmpcc_enable_transmitter(ch);
|
|
break;
|
|
|
|
case TIOCCBRK:
|
|
SET(ch->ch_flags, CLMPCC_FLG_END_BREAK);
|
|
clmpcc_enable_transmitter(ch);
|
|
break;
|
|
|
|
case TIOCSDTR:
|
|
clmpcc_modem_control(ch, TIOCM_DTR, DMBIS);
|
|
break;
|
|
|
|
case TIOCCDTR:
|
|
clmpcc_modem_control(ch, TIOCM_DTR, DMBIC);
|
|
break;
|
|
|
|
case TIOCMSET:
|
|
clmpcc_modem_control(ch, *((int *)data), DMSET);
|
|
break;
|
|
|
|
case TIOCMBIS:
|
|
clmpcc_modem_control(ch, *((int *)data), DMBIS);
|
|
break;
|
|
|
|
case TIOCMBIC:
|
|
clmpcc_modem_control(ch, *((int *)data), DMBIC);
|
|
break;
|
|
|
|
case TIOCMGET:
|
|
*((int *)data) = clmpcc_modem_control(ch, 0, DMGET);
|
|
break;
|
|
|
|
case TIOCGFLAGS:
|
|
*((int *)data) = ch->ch_openflags;
|
|
break;
|
|
|
|
case TIOCSFLAGS:
|
|
error = suser(p->p_ucred, &p->p_acflag);
|
|
if ( error )
|
|
break;
|
|
ch->ch_openflags = *((int *)data) &
|
|
(TIOCFLAG_SOFTCAR | TIOCFLAG_CLOCAL |
|
|
TIOCFLAG_CRTSCTS | TIOCFLAG_MDMBUF);
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) )
|
|
SET(ch->ch_openflags, TIOCFLAG_SOFTCAR);
|
|
break;
|
|
|
|
default:
|
|
error = ENOTTY;
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
clmpcc_modem_control(ch, bits, howto)
|
|
struct clmpcc_chan *ch;
|
|
int bits;
|
|
int howto;
|
|
{
|
|
struct clmpcc_softc *sc = ch->ch_sc;
|
|
struct tty *tp = ch->ch_tty;
|
|
int oldch;
|
|
int msvr;
|
|
int rbits = 0;
|
|
|
|
oldch = clmpcc_select_channel(sc, ch->ch_car);
|
|
|
|
switch ( howto ) {
|
|
case DMGET:
|
|
msvr = clmpcc_rd_msvr(sc);
|
|
|
|
if ( sc->sc_swaprtsdtr ) {
|
|
rbits |= (msvr & CLMPCC_MSVR_RTS) ? TIOCM_DTR : 0;
|
|
rbits |= (msvr & CLMPCC_MSVR_DTR) ? TIOCM_RTS : 0;
|
|
} else {
|
|
rbits |= (msvr & CLMPCC_MSVR_RTS) ? TIOCM_RTS : 0;
|
|
rbits |= (msvr & CLMPCC_MSVR_DTR) ? TIOCM_DTR : 0;
|
|
}
|
|
|
|
rbits |= (msvr & CLMPCC_MSVR_CTS) ? TIOCM_CTS : 0;
|
|
rbits |= (msvr & CLMPCC_MSVR_CD) ? TIOCM_CD : 0;
|
|
rbits |= (msvr & CLMPCC_MSVR_DSR) ? TIOCM_DSR : 0;
|
|
break;
|
|
|
|
case DMSET:
|
|
if ( sc->sc_swaprtsdtr ) {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR,
|
|
bits & TIOCM_RTS ? CLMPCC_MSVR_DTR : 0);
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS,
|
|
bits & TIOCM_DTR ? CLMPCC_MSVR_RTS : 0);
|
|
} else {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS,
|
|
bits & TIOCM_RTS ? CLMPCC_MSVR_RTS : 0);
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR,
|
|
bits & TIOCM_DTR ? CLMPCC_MSVR_DTR : 0);
|
|
}
|
|
break;
|
|
|
|
case DMBIS:
|
|
if ( sc->sc_swaprtsdtr ) {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) && ISSET(bits, TIOCM_RTS) )
|
|
clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_DTR, CLMPCC_MSVR_DTR);
|
|
if ( ISSET(bits, TIOCM_DTR) )
|
|
clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_RTS, CLMPCC_MSVR_RTS);
|
|
} else {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) && ISSET(bits, TIOCM_RTS) )
|
|
clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_RTS, CLMPCC_MSVR_RTS);
|
|
if ( ISSET(bits, TIOCM_DTR) )
|
|
clmpcc_wr_msvr(sc,CLMPCC_REG_MSVR_DTR, CLMPCC_MSVR_DTR);
|
|
}
|
|
break;
|
|
|
|
case DMBIC:
|
|
if ( sc->sc_swaprtsdtr ) {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) && ISCLR(bits, TIOCM_RTS) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, 0);
|
|
if ( ISCLR(bits, TIOCM_DTR) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, 0);
|
|
} else {
|
|
if ( ISCLR(tp->t_cflag, CRTSCTS) && ISCLR(bits, TIOCM_RTS) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_RTS, 0);
|
|
if ( ISCLR(bits, TIOCM_DTR) )
|
|
clmpcc_wr_msvr(sc, CLMPCC_REG_MSVR_DTR, 0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
clmpcc_select_channel(sc, oldch);
|
|
|
|
return rbits;
|
|
}
|
|
|
|
static int
|
|
clmpcc_param(tp, t)
|
|
struct tty *tp;
|
|
struct termios *t;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(tp->t_dev)];
|
|
struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)];
|
|
u_char cor;
|
|
u_char oldch;
|
|
int oclk, obpr;
|
|
int iclk, ibpr;
|
|
int s;
|
|
|
|
/* Check requested parameters. */
|
|
if ( t->c_ospeed && clmpcc_speed(sc, t->c_ospeed, &oclk, &obpr) < 0 )
|
|
return EINVAL;
|
|
|
|
if ( t->c_ispeed && clmpcc_speed(sc, t->c_ispeed, &iclk, &ibpr) < 0 )
|
|
return EINVAL;
|
|
|
|
/*
|
|
* For the console, always force CLOCAL and !HUPCL, so that the port
|
|
* is always active.
|
|
*/
|
|
if ( ISSET(ch->ch_openflags, TIOCFLAG_SOFTCAR) ||
|
|
ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) ) {
|
|
SET(t->c_cflag, CLOCAL);
|
|
CLR(t->c_cflag, HUPCL);
|
|
}
|
|
|
|
CLR(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS);
|
|
|
|
/* If ospeed it zero, hangup the line */
|
|
clmpcc_modem_control(ch, TIOCM_DTR, t->c_ospeed == 0 ? DMBIC : DMBIS);
|
|
|
|
if ( t->c_ospeed ) {
|
|
ch->ch_tcor = CLMPCC_TCOR_CLK(oclk);
|
|
ch->ch_tbpr = obpr;
|
|
} else {
|
|
ch->ch_tcor = 0;
|
|
ch->ch_tbpr = 0;
|
|
}
|
|
|
|
if ( t->c_ispeed ) {
|
|
ch->ch_rcor = CLMPCC_RCOR_CLK(iclk);
|
|
ch->ch_rbpr = ibpr;
|
|
} else {
|
|
ch->ch_rcor = 0;
|
|
ch->ch_rbpr = 0;
|
|
}
|
|
|
|
/* Work out value to use for COR1 */
|
|
cor = 0;
|
|
if ( ISSET(t->c_cflag, PARENB) ) {
|
|
cor |= CLMPCC_COR1_NORM_PARITY;
|
|
if ( ISSET(t->c_cflag, PARODD) )
|
|
cor |= CLMPCC_COR1_ODD_PARITY;
|
|
}
|
|
|
|
if ( ISCLR(t->c_cflag, INPCK) )
|
|
cor |= CLMPCC_COR1_IGNORE_PAR;
|
|
|
|
switch ( t->c_cflag & CSIZE ) {
|
|
case CS5:
|
|
cor |= CLMPCC_COR1_CHAR_5BITS;
|
|
break;
|
|
|
|
case CS6:
|
|
cor |= CLMPCC_COR1_CHAR_6BITS;
|
|
break;
|
|
|
|
case CS7:
|
|
cor |= CLMPCC_COR1_CHAR_7BITS;
|
|
break;
|
|
|
|
case CS8:
|
|
cor |= CLMPCC_COR1_CHAR_8BITS;
|
|
break;
|
|
}
|
|
|
|
ch->ch_cor1 = cor;
|
|
|
|
/*
|
|
* The only interesting bit in COR2 is 'CTS Automatic Enable'
|
|
* when hardware flow control is in effect.
|
|
*/
|
|
ch->ch_cor2 = ISSET(t->c_cflag, CRTSCTS) ? CLMPCC_COR2_CtsAE : 0;
|
|
|
|
/* COR3 needs to be set to the number of stop bits... */
|
|
ch->ch_cor3 = ISSET(t->c_cflag, CSTOPB) ? CLMPCC_COR3_STOP_2 :
|
|
CLMPCC_COR3_STOP_1;
|
|
|
|
/*
|
|
* COR4 contains the FIFO threshold setting.
|
|
* We adjust the threshold depending on the input speed...
|
|
*/
|
|
if ( t->c_ispeed <= 1200 )
|
|
ch->ch_cor4 = CLMPCC_COR4_FIFO_LOW;
|
|
else if ( t->c_ispeed <= 19200 )
|
|
ch->ch_cor4 = CLMPCC_COR4_FIFO_MED;
|
|
else
|
|
ch->ch_cor4 = CLMPCC_COR4_FIFO_HIGH;
|
|
|
|
/*
|
|
* If chip is used with CTS and DTR swapped, we can enable
|
|
* automatic hardware flow control.
|
|
*/
|
|
if ( sc->sc_swaprtsdtr && ISSET(t->c_cflag, CRTSCTS) )
|
|
ch->ch_cor5 = CLMPCC_COR5_FLOW_NORM;
|
|
else
|
|
ch->ch_cor5 = 0;
|
|
|
|
s = splserial();
|
|
oldch = clmpcc_select_channel(sc, ch->ch_car);
|
|
|
|
/*
|
|
* COR2 needs to be set immediately otherwise we might never get
|
|
* a Tx EMPTY interrupt to change the other parameters.
|
|
*/
|
|
if ( clmpcc_rdreg(sc, CLMPCC_REG_COR2) != ch->ch_cor2 )
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2);
|
|
|
|
if ( ISCLR(ch->ch_tty->t_state, TS_BUSY) )
|
|
clmpcc_set_params(ch);
|
|
else
|
|
SET(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS);
|
|
|
|
clmpcc_select_channel(sc, oldch);
|
|
|
|
splx(s);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
clmpcc_set_params(ch)
|
|
struct clmpcc_chan *ch;
|
|
{
|
|
struct clmpcc_softc *sc = ch->ch_sc;
|
|
u_char r1;
|
|
u_char r2;
|
|
|
|
if ( ch->ch_tcor || ch->ch_tbpr ) {
|
|
r1 = clmpcc_rdreg(sc, CLMPCC_REG_TCOR);
|
|
r2 = clmpcc_rdreg(sc, CLMPCC_REG_TBPR);
|
|
/* Only write Tx rate if it really has changed */
|
|
if ( ch->ch_tcor != r1 || ch->ch_tbpr != r2 ) {
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TCOR, ch->ch_tcor);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TBPR, ch->ch_tbpr);
|
|
}
|
|
}
|
|
|
|
if ( ch->ch_rcor || ch->ch_rbpr ) {
|
|
r1 = clmpcc_rdreg(sc, CLMPCC_REG_RCOR);
|
|
r2 = clmpcc_rdreg(sc, CLMPCC_REG_RBPR);
|
|
/* Only write Rx rate if it really has changed */
|
|
if ( ch->ch_rcor != r1 || ch->ch_rbpr != r2 ) {
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RCOR, ch->ch_rcor);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_RBPR, ch->ch_rbpr);
|
|
}
|
|
}
|
|
|
|
if ( clmpcc_rdreg(sc, CLMPCC_REG_COR1) != ch->ch_cor1 ) {
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR1, ch->ch_cor1);
|
|
/* Any change to COR1 requires an INIT command */
|
|
SET(ch->ch_flags, CLMPCC_FLG_NEED_INIT);
|
|
}
|
|
|
|
if ( clmpcc_rdreg(sc, CLMPCC_REG_COR3) != ch->ch_cor3 )
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR3, ch->ch_cor3);
|
|
|
|
r1 = clmpcc_rdreg(sc, CLMPCC_REG_COR4);
|
|
if ( ch->ch_cor4 != (r1 & CLMPCC_COR4_FIFO_MASK) ) {
|
|
/*
|
|
* Note: If the FIFO has changed, we always set it to
|
|
* zero here and disable the Receive Timeout interrupt.
|
|
* It's up to the Rx Interrupt handler to pick the
|
|
* appropriate moment to write the new FIFO length.
|
|
*/
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR4, r1 & ~CLMPCC_COR4_FIFO_MASK);
|
|
r1 = clmpcc_rdreg(sc, CLMPCC_REG_IER);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, r1 & ~CLMPCC_IER_RET);
|
|
SET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR);
|
|
}
|
|
|
|
r1 = clmpcc_rdreg(sc, CLMPCC_REG_COR5);
|
|
if ( ch->ch_cor5 != (r1 & CLMPCC_COR5_FLOW_MASK) ) {
|
|
r1 &= ~CLMPCC_COR5_FLOW_MASK;
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR5, r1 | ch->ch_cor5);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clmpcc_start(tp)
|
|
struct tty *tp;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(tp->t_dev)];
|
|
struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)];
|
|
u_int oldch;
|
|
int s;
|
|
|
|
s = spltty();
|
|
|
|
if ( ISCLR(tp->t_state, TS_TTSTOP | TS_TIMEOUT | TS_BUSY) ) {
|
|
if ( tp->t_outq.c_cc <= tp->t_lowat ) {
|
|
if ( ISSET(tp->t_state, TS_ASLEEP) ) {
|
|
CLR(tp->t_state, TS_ASLEEP);
|
|
wakeup(&tp->t_outq);
|
|
}
|
|
selwakeup(&tp->t_wsel);
|
|
}
|
|
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_START_BREAK |
|
|
CLMPCC_FLG_END_BREAK) ||
|
|
tp->t_outq.c_cc > 0 ) {
|
|
|
|
if ( ISCLR(ch->ch_flags, CLMPCC_FLG_START_BREAK |
|
|
CLMPCC_FLG_END_BREAK) ) {
|
|
ch->ch_obuf_addr = tp->t_outq.c_cf;
|
|
ch->ch_obuf_size = ndqb(&tp->t_outq, 0);
|
|
}
|
|
|
|
/* Enable TX empty interrupts */
|
|
oldch = clmpcc_select_channel(ch->ch_sc, ch->ch_car);
|
|
clmpcc_wrreg(ch->ch_sc, CLMPCC_REG_IER,
|
|
clmpcc_rdreg(ch->ch_sc, CLMPCC_REG_IER) |
|
|
CLMPCC_IER_TX_EMPTY);
|
|
clmpcc_select_channel(ch->ch_sc, oldch);
|
|
SET(tp->t_state, TS_BUSY);
|
|
}
|
|
}
|
|
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* Stop output on a line.
|
|
*/
|
|
void
|
|
clmpccstop(tp, flag)
|
|
struct tty *tp;
|
|
int flag;
|
|
{
|
|
struct clmpcc_softc *sc = clmpcc_cd.cd_devs[CLMPCCUNIT(tp->t_dev)];
|
|
struct clmpcc_chan *ch = &sc->sc_chans[CLMPCCCHAN(tp->t_dev)];
|
|
int s;
|
|
|
|
s = splserial();
|
|
|
|
if ( ISSET(tp->t_state, TS_BUSY) ) {
|
|
if ( ISCLR(tp->t_state, TS_TTSTOP) )
|
|
SET(tp->t_state, TS_FLUSH);
|
|
ch->ch_obuf_size = 0;
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
/*
|
|
* RX interrupt routine
|
|
*/
|
|
int
|
|
clmpcc_rxintr(arg)
|
|
void *arg;
|
|
{
|
|
struct clmpcc_softc *sc = (struct clmpcc_softc *)arg;
|
|
struct clmpcc_chan *ch;
|
|
u_int8_t *put, *end, rxd;
|
|
u_char errstat;
|
|
u_char fc, tc;
|
|
u_char risr;
|
|
u_char rir;
|
|
#ifdef DDB
|
|
int saw_break = 0;
|
|
#endif
|
|
|
|
/* Receive interrupt active? */
|
|
rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR);
|
|
|
|
/*
|
|
* If we're using auto-vectored interrupts, we have to
|
|
* verify if the chip is generating the interrupt.
|
|
*/
|
|
if ( sc->sc_vector_base == 0 && (rir & CLMPCC_RIR_RACT) == 0 )
|
|
return 0;
|
|
|
|
/* Get pointer to interrupting channel's data structure */
|
|
ch = &sc->sc_chans[rir & CLMPCC_RIR_RCN_MASK];
|
|
|
|
/* Get the interrupt status register */
|
|
risr = clmpcc_rdreg(sc, CLMPCC_REG_RISRl);
|
|
if ( risr & CLMPCC_RISR_TIMEOUT ) {
|
|
u_char reg;
|
|
/*
|
|
* Set the FIFO threshold to zero, and disable
|
|
* further receive timeout interrupts.
|
|
*/
|
|
reg = clmpcc_rdreg(sc, CLMPCC_REG_COR4);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR4, reg & ~CLMPCC_COR4_FIFO_MASK);
|
|
reg = clmpcc_rdreg(sc, CLMPCC_REG_IER);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, reg & ~CLMPCC_IER_RET);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_REOIR, CLMPCC_REOIR_NO_TRANS);
|
|
SET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR);
|
|
return 1;
|
|
}
|
|
|
|
/* How many bytes are waiting in the FIFO? */
|
|
fc = tc = clmpcc_rdreg(sc, CLMPCC_REG_RFOC) & CLMPCC_RFOC_MASK;
|
|
|
|
#ifdef DDB
|
|
/*
|
|
* Allow BREAK on the console to drop to the debugger.
|
|
*/
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_IS_CONSOLE) &&
|
|
risr & CLMPCC_RISR_BREAK ) {
|
|
saw_break = 1;
|
|
}
|
|
#endif
|
|
|
|
if ( ISCLR(ch->ch_tty->t_state, TS_ISOPEN) && fc ) {
|
|
/* Just get rid of the data */
|
|
while ( fc-- )
|
|
(void) clmpcc_rd_rxdata(sc);
|
|
goto rx_done;
|
|
}
|
|
|
|
put = ch->ch_ibuf_wr;
|
|
end = ch->ch_ibuf_end;
|
|
|
|
/*
|
|
* Note: The chip is completely hosed WRT these error
|
|
* conditions; there seems to be no way to associate
|
|
* the error with the correct character in the FIFO.
|
|
* We compromise by tagging the first character we read
|
|
* with the error. Not perfect, but there's no other way.
|
|
*/
|
|
errstat = 0;
|
|
if ( risr & CLMPCC_RISR_PARITY )
|
|
errstat |= TTY_PE;
|
|
if ( risr & (CLMPCC_RISR_FRAMING | CLMPCC_RISR_BREAK) )
|
|
errstat |= TTY_FE;
|
|
|
|
/*
|
|
* As long as there are characters in the FIFO, and we
|
|
* have space for them...
|
|
*/
|
|
while ( fc > 0 ) {
|
|
|
|
*put++ = rxd = clmpcc_rd_rxdata(sc);
|
|
*put++ = errstat;
|
|
|
|
if ( put >= end )
|
|
put = ch->ch_ibuf;
|
|
|
|
if ( put == ch->ch_ibuf_rd ) {
|
|
put -= 2;
|
|
if ( put < ch->ch_ibuf )
|
|
put = end - 2;
|
|
}
|
|
|
|
errstat = 0;
|
|
fc--;
|
|
}
|
|
|
|
ch->ch_ibuf_wr = put;
|
|
|
|
#if 0
|
|
if ( sc->sc_swaprtsdtr == 0 &&
|
|
ISSET(cy->cy_tty->t_cflag, CRTSCTS) && cc < ch->ch_r_hiwat) {
|
|
/*
|
|
* If RTS/DTR are not physically swapped, we have to
|
|
* do hardware flow control manually
|
|
*/
|
|
clmpcc_wr_msvr(sc, CLMPCC_MSVR_RTS, 0);
|
|
}
|
|
#endif
|
|
|
|
rx_done:
|
|
if ( fc != tc ) {
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR) ) {
|
|
u_char reg;
|
|
/*
|
|
* Set the FIFO threshold to the preset value,
|
|
* and enable receive timeout interrupts.
|
|
*/
|
|
reg = clmpcc_rdreg(sc, CLMPCC_REG_COR4);
|
|
reg = (reg & ~CLMPCC_COR4_FIFO_MASK) | ch->ch_cor4;
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR4, reg);
|
|
reg = clmpcc_rdreg(sc, CLMPCC_REG_IER);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, reg | CLMPCC_IER_RET);
|
|
CLR(ch->ch_flags, CLMPCC_FLG_FIFO_CLEAR);
|
|
}
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_REOIR, 0);
|
|
if ( sc->sc_soft_running == 0 ) {
|
|
sc->sc_soft_running = 1;
|
|
(sc->sc_softhook)(sc);
|
|
}
|
|
} else
|
|
clmpcc_wrreg(sc, CLMPCC_REG_REOIR, CLMPCC_REOIR_NO_TRANS);
|
|
|
|
#ifdef DDB
|
|
/*
|
|
* Only =after= we write REOIR is it safe to drop to the debugger.
|
|
*/
|
|
if ( saw_break )
|
|
Debugger();
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Tx interrupt routine
|
|
*/
|
|
int
|
|
clmpcc_txintr(arg)
|
|
void *arg;
|
|
{
|
|
struct clmpcc_softc *sc = (struct clmpcc_softc *)arg;
|
|
struct clmpcc_chan *ch;
|
|
struct tty *tp;
|
|
u_char ftc, oftc;
|
|
u_char tir, teoir;
|
|
int etcmode = 0;
|
|
|
|
/* Tx interrupt active? */
|
|
tir = clmpcc_rdreg(sc, CLMPCC_REG_TIR);
|
|
|
|
/*
|
|
* If we're using auto-vectored interrupts, we have to
|
|
* verify if the chip is generating the interrupt.
|
|
*/
|
|
if ( sc->sc_vector_base == 0 && (tir & CLMPCC_TIR_TACT) == 0 )
|
|
return 0;
|
|
|
|
/* Get pointer to interrupting channel's data structure */
|
|
ch = &sc->sc_chans[tir & CLMPCC_TIR_TCN_MASK];
|
|
tp = ch->ch_tty;
|
|
|
|
/* Dummy read of the interrupt status register */
|
|
(void) clmpcc_rdreg(sc, CLMPCC_REG_TISR);
|
|
|
|
/* Make sure embedded transmit commands are disabled */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR2, ch->ch_cor2);
|
|
|
|
ftc = oftc = clmpcc_rdreg(sc, CLMPCC_REG_TFTC);
|
|
|
|
/* Handle a delayed parameter change */
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS) ) {
|
|
CLR(ch->ch_flags, CLMPCC_FLG_UPDATE_PARMS);
|
|
clmpcc_set_params(ch);
|
|
}
|
|
|
|
if ( ch->ch_obuf_size > 0 ) {
|
|
u_int n = min(ch->ch_obuf_size, ftc);
|
|
|
|
clmpcc_wrtx_multi(sc, ch->ch_obuf_addr, n);
|
|
|
|
ftc -= n;
|
|
ch->ch_obuf_size -= n;
|
|
ch->ch_obuf_addr += n;
|
|
|
|
} else {
|
|
/*
|
|
* Check if we should start/stop a break
|
|
*/
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_START_BREAK) ) {
|
|
CLR(ch->ch_flags, CLMPCC_FLG_START_BREAK);
|
|
/* Enable embedded transmit commands */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR2,
|
|
ch->ch_cor2 | CLMPCC_COR2_ETC);
|
|
clmpcc_wr_txdata(sc, CLMPCC_ETC_MAGIC);
|
|
clmpcc_wr_txdata(sc, CLMPCC_ETC_SEND_BREAK);
|
|
ftc -= 2;
|
|
etcmode = 1;
|
|
}
|
|
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_END_BREAK) ) {
|
|
CLR(ch->ch_flags, CLMPCC_FLG_END_BREAK);
|
|
/* Enable embedded transmit commands */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_COR2,
|
|
ch->ch_cor2 | CLMPCC_COR2_ETC);
|
|
clmpcc_wr_txdata(sc, CLMPCC_ETC_MAGIC);
|
|
clmpcc_wr_txdata(sc, CLMPCC_ETC_STOP_BREAK);
|
|
ftc -= 2;
|
|
etcmode = 1;
|
|
}
|
|
}
|
|
|
|
tir = clmpcc_rdreg(sc, CLMPCC_REG_IER);
|
|
|
|
if ( ftc != oftc ) {
|
|
/*
|
|
* Enable/disable the Tx FIFO threshold interrupt
|
|
* according to how much data is in the FIFO.
|
|
* However, always disable the FIFO threshold if
|
|
* we've left the channel in 'Embedded Transmit
|
|
* Command' mode.
|
|
*/
|
|
if ( etcmode || ftc >= ch->ch_cor4 )
|
|
tir &= ~CLMPCC_IER_TX_FIFO;
|
|
else
|
|
tir |= CLMPCC_IER_TX_FIFO;
|
|
teoir = 0;
|
|
} else {
|
|
/*
|
|
* No data was sent.
|
|
* Disable transmit interrupt.
|
|
*/
|
|
tir &= ~(CLMPCC_IER_TX_EMPTY|CLMPCC_IER_TX_FIFO);
|
|
teoir = CLMPCC_TEOIR_NO_TRANS;
|
|
|
|
/*
|
|
* Request Tx processing in the soft interrupt handler
|
|
*/
|
|
ch->ch_tx_done = 1;
|
|
if ( ! sc->sc_soft_running ) {
|
|
sc->sc_soft_running = 1;
|
|
(sc->sc_softhook)(sc);
|
|
}
|
|
}
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, tir);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_TEOIR, teoir);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Modem change interrupt routine
|
|
*/
|
|
int
|
|
clmpcc_mdintr(arg)
|
|
void *arg;
|
|
{
|
|
struct clmpcc_softc *sc = (struct clmpcc_softc *)arg;
|
|
u_char mir;
|
|
|
|
/* Modem status interrupt active? */
|
|
mir = clmpcc_rdreg(sc, CLMPCC_REG_MIR);
|
|
|
|
/*
|
|
* If we're using auto-vectored interrupts, we have to
|
|
* verify if the chip is generating the interrupt.
|
|
*/
|
|
if ( sc->sc_vector_base == 0 && (mir & CLMPCC_MIR_MACT) == 0 )
|
|
return 0;
|
|
|
|
/* Dummy read of the interrupt status register */
|
|
(void) clmpcc_rdreg(sc, CLMPCC_REG_MISR);
|
|
|
|
/* Retrieve current status of modem lines. */
|
|
sc->sc_chans[mir & CLMPCC_MIR_MCN_MASK].ch_control |=
|
|
clmpcc_rd_msvr(sc) & CLMPCC_MSVR_CD;
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_MEOIR, 0);
|
|
|
|
if ( sc->sc_soft_running == 0 ) {
|
|
sc->sc_soft_running = 1;
|
|
(sc->sc_softhook)(sc);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
clmpcc_softintr(arg)
|
|
void *arg;
|
|
{
|
|
struct clmpcc_softc *sc = (struct clmpcc_softc *)arg;
|
|
struct clmpcc_chan *ch;
|
|
struct tty *tp;
|
|
int (*rint) __P((int, struct tty *));
|
|
u_char *get;
|
|
u_char reg;
|
|
u_int c;
|
|
int chan;
|
|
|
|
sc->sc_soft_running = 0;
|
|
|
|
/* Handle Modem state changes too... */
|
|
|
|
for (chan = 0; chan < CLMPCC_NUM_CHANS; chan++) {
|
|
ch = &sc->sc_chans[chan];
|
|
tp = ch->ch_tty;
|
|
|
|
get = ch->ch_ibuf_rd;
|
|
rint = linesw[tp->t_line].l_rint;
|
|
|
|
/* Squirt buffered incoming data into the tty layer */
|
|
while ( get != ch->ch_ibuf_wr ) {
|
|
c = get[0];
|
|
c |= ((u_int)get[1]) << 8;
|
|
if ( (rint)(c, tp) == -1 ) {
|
|
ch->ch_ibuf_rd = ch->ch_ibuf_wr;
|
|
break;
|
|
}
|
|
|
|
get += 2;
|
|
if ( get == ch->ch_ibuf_end )
|
|
get = ch->ch_ibuf;
|
|
|
|
ch->ch_ibuf_rd = get;
|
|
}
|
|
|
|
/*
|
|
* Is the transmitter idle and in need of attention?
|
|
*/
|
|
if ( ch->ch_tx_done ) {
|
|
ch->ch_tx_done = 0;
|
|
|
|
if ( ISSET(ch->ch_flags, CLMPCC_FLG_NEED_INIT) ) {
|
|
clmpcc_channel_cmd(sc, ch->ch_car,
|
|
CLMPCC_CCR_T0_INIT |
|
|
CLMPCC_CCR_T0_RX_EN |
|
|
CLMPCC_CCR_T0_TX_EN);
|
|
CLR(ch->ch_flags, CLMPCC_FLG_NEED_INIT);
|
|
|
|
/*
|
|
* Allow time for the channel to initialise.
|
|
* (Empirically derived duration; there must
|
|
* be another way to determine the command
|
|
* has completed without busy-waiting...)
|
|
*/
|
|
delay(800);
|
|
|
|
/*
|
|
* Update the tty layer's idea of the carrier
|
|
* bit, in case we changed CLOCAL or MDMBUF.
|
|
* We don't hang up here; we only do that by
|
|
* explicit request.
|
|
*/
|
|
reg = clmpcc_rd_msvr(sc) & CLMPCC_MSVR_CD;
|
|
(*linesw[tp->t_line].l_modem)(tp, reg != 0);
|
|
}
|
|
|
|
CLR(tp->t_state, TS_BUSY);
|
|
if ( ISSET(tp->t_state, TS_FLUSH) )
|
|
CLR(tp->t_state, TS_FLUSH);
|
|
else
|
|
ndflush(&tp->t_outq,
|
|
(int)(ch->ch_obuf_addr - tp->t_outq.c_cf));
|
|
|
|
(*linesw[tp->t_line].l_start)(tp);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/
|
|
/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/
|
|
/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*/
|
|
/*
|
|
* Following are all routines needed for a cd240x channel to act as console
|
|
*/
|
|
int
|
|
clmpcc_cnattach(sc, chan, rate)
|
|
struct clmpcc_softc *sc;
|
|
int chan;
|
|
int rate;
|
|
{
|
|
cons_sc = sc;
|
|
cons_chan = chan;
|
|
cons_rate = rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The following functions are polled getc and putc routines, for console use.
|
|
*/
|
|
static int
|
|
clmpcc_common_getc(sc, chan)
|
|
struct clmpcc_softc *sc;
|
|
int chan;
|
|
{
|
|
u_char old_chan;
|
|
u_char old_ier;
|
|
u_char ch, rir, risr;
|
|
int s;
|
|
|
|
s = splhigh();
|
|
|
|
/* Save the currently active channel */
|
|
old_chan = clmpcc_select_channel(sc, chan);
|
|
|
|
/*
|
|
* We have to put the channel into RX interrupt mode before
|
|
* trying to read the Rx data register. So save the previous
|
|
* interrupt mode.
|
|
*/
|
|
old_ier = clmpcc_rdreg(sc, CLMPCC_REG_IER);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, CLMPCC_IER_RX_FIFO);
|
|
|
|
/* Loop until we get a character */
|
|
for (;;) {
|
|
/*
|
|
* The REN bit will be set in the Receive Interrupt Register
|
|
* when the CD240x has a character to process. Remember,
|
|
* the RACT bit won't be set until we generate an interrupt
|
|
* acknowledge cycle via the MD front-end.
|
|
*/
|
|
rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR);
|
|
if ( (rir & CLMPCC_RIR_REN) == 0 )
|
|
continue;
|
|
|
|
/* Acknowledge the request */
|
|
if ( sc->sc_iackhook )
|
|
(sc->sc_iackhook)(sc, CLMPCC_IACK_RX);
|
|
|
|
/*
|
|
* Determine if the interrupt is for the required channel
|
|
* and if valid data is available.
|
|
*/
|
|
rir = clmpcc_rdreg(sc, CLMPCC_REG_RIR);
|
|
risr = clmpcc_rdreg(sc, CLMPCC_REG_RISR);
|
|
if ( (rir & CLMPCC_RIR_RCN_MASK) != chan ||
|
|
risr != 0 ) {
|
|
/* Rx error, or BREAK */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_REOIR,
|
|
CLMPCC_REOIR_NO_TRANS);
|
|
} else {
|
|
/* Dummy read of the FIFO count register */
|
|
(void) clmpcc_rdreg(sc, CLMPCC_REG_RFOC);
|
|
|
|
/* Fetch the received character */
|
|
ch = clmpcc_rd_rxdata(sc);
|
|
|
|
clmpcc_wrreg(sc, CLMPCC_REG_REOIR, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Restore the original IER and CAR register contents */
|
|
clmpcc_wrreg(sc, CLMPCC_REG_IER, old_ier);
|
|
clmpcc_select_channel(sc, old_chan);
|
|
|
|
splx(s);
|
|
return ch;
|
|
}
|
|
|
|
|
|
static void
|
|
clmpcc_common_putc(sc, chan, c)
|
|
struct clmpcc_softc *sc;
|
|
int chan;
|
|
int c;
|
|
{
|
|
u_char old_chan;
|
|
int s = splhigh();
|
|
|
|
/* Save the currently active channel */
|
|
old_chan = clmpcc_select_channel(sc, chan);
|
|
|
|
/*
|
|
* Since we can only access the Tx Data register from within
|
|
* the interrupt handler, the easiest way to get console data
|
|
* onto the wire is using one of the Special Transmit Character
|
|
* registers.
|
|
*/
|
|
clmpcc_wrreg(sc, CLMPCC_REG_SCHR4, c);
|
|
clmpcc_wrreg(sc, CLMPCC_REG_STCR, CLMPCC_STCR_SSPC(4) |
|
|
CLMPCC_STCR_SND_SPC);
|
|
|
|
/* Wait until the "Send Special Character" command is accepted */
|
|
while ( clmpcc_rdreg(sc, CLMPCC_REG_STCR) != 0 )
|
|
;
|
|
|
|
/* Restore the previous channel selected */
|
|
clmpcc_select_channel(sc, old_chan);
|
|
|
|
splx(s);
|
|
}
|
|
|
|
int
|
|
clmpcccngetc(dev)
|
|
dev_t dev;
|
|
{
|
|
return clmpcc_common_getc(cons_sc, cons_chan);
|
|
}
|
|
|
|
/*
|
|
* Console kernel output character routine.
|
|
*/
|
|
void
|
|
clmpcccnputc(dev, c)
|
|
dev_t dev;
|
|
int c;
|
|
{
|
|
if ( c == '\n' )
|
|
clmpcc_common_putc(cons_sc, cons_chan, '\r');
|
|
|
|
clmpcc_common_putc(cons_sc, cons_chan, c);
|
|
}
|