Messages in this thread Patch in this message | | | Subject | [RFC][PATCH 9/13] Equinox SST driver: tty interface | Date | Thu, 22 Jun 2006 09:20:28 -0400 | From | "Straub, Michael" <> |
| |
Adds Equinox multi-port serial (SST) driver.
Part 9: new source file: drivers/char/eqnx/sst_tty.c. Provides the standard TTY interface routines for the SST driver.
Signed-off-by: Mike Straub <michael.straub@avocent.com>
--- sst_tty.c | 1961 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 1961 insertions(+)
diff -Naurp -X dontdiff linux-2.6.17/drivers/char/eqnx/sst_tty.c linux-2.6.17.eqnx/drivers/char/eqnx/sst_tty.c --- linux-2.6.17/drivers/char/eqnx/sst_tty.c 1969-12-31 19:00:00.000000000 -0500 +++ linux-2.6.17.eqnx/drivers/char/eqnx/sst_tty.c 2006-06-20 09:50:09.000000000 -0400 @@ -0,0 +1,1961 @@ +/* + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +/* + * This driver supports the PCI models of the Equinox / Avocent SST boards + * using SSP-4 and SSP-64 ASIC technology + * Boards supported: + * SSP-4P + * SSP-8P + * SSP-16P + * SSP-64P + * SSP-128P + * + * Currently maintained by mike straub <michael.straub@avocent.com> + */ + +/* + * standard tty interface routines + */ + +#include <linux/config.h> +#include <linux/version.h> + +#ifdef CONFIG_MODVERSIONS +#define MODVERSIONS 1 +#endif + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <asm/uaccess.h> +#include <linux/device.h> + +#include "icp.h" +#include "eqnx_def.h" +#include "eqnx.h" + +/********************************************************************** *****/ +/* module globals and defines */ +/********************************************************************** *****/ + +/* defer call to write_wakeup */ +int eqnx_write_wakeup_deferred = 0; + +/* local buffer for copying output characters. Used in eqnx_put_char */ +extern char *eqnx_txcookbuf; +static int eqnx_txcooksize = 0; +static int eqnx_txcookrealsize = 0; +static struct tty_struct *eqnx_txcooktty = (struct tty_struct *)NULL; + +#define MY_GROUP() (process_group(current)) + +static void chanon(struct mpchan *mpc); +static void chanoff(struct mpchan *mpc); +static void eqnx_write_SSP64(struct mpchan *mpc, unsigned char *buf, int count); +void eqnx_write_SSP4(struct mpchan *mpc, int func_type); +void eqnx_flush_chars(struct tty_struct *tty); +static void eqnx_flush_chars_locked(struct tty_struct *tty); +void eqnx_flush_buffer_locked(struct tty_struct *tty); +static void delay_jiffies(int); +extern int SSTMINOR(unsigned int, unsigned int); + +/********************************************************************** *****/ +/* external variable and routines */ +/********************************************************************** *****/ + +extern int eqnx_nssps; +extern struct mpdev eqnx_dev[MAXSSP]; +extern struct mpchan *eqnx_chan; +extern struct datascope eqnx_dscope[2]; +extern char *eqnx_tmpwritebuf; +extern struct semaphore eqnx_tmpwritesem; + +extern void eqnx_megaparam(int); +extern int eqnx_modem(int, int); +extern void eqnx_frame_wait(struct mpchan *, int); +extern void eqnx_chnl_sync(struct mpchan *); +extern int SSTMINOR(unsigned int, unsigned int); +extern u32 eqnx_get_modem_info(struct mpchan *); +extern void eqnx_set_modem_info(struct mpchan *, unsigned int, unsigned int, + struct tty_struct *); + +/* + * eqnx_open(tty, filp) + * + * Open of tty device associated with SST channel. + * + * tty = pointer to tty structure being opened. + * filp = file structure. + */ +int eqnx_open(struct tty_struct *tty, struct file *filp) +{ + struct mpchan *mpc; + struct tty_struct *tp; + struct mpdev *mpd; + int d, minor, major, nchan, rc = 0; + unsigned long flags; + + major = tty->driver->major; + minor = tty->driver->minor_start + tty->index; + + d = SSTMINOR(major, minor); + + /* validate channel number */ + if ((d > (eqnx_nssps * MAXCHNL_BRD)) || (eqnx_nssps == 0) || (d < 0)) + return (-ENODEV); + + mpc = &eqnx_chan[d]; + mpd = mpc->mpc_mpd; + + /* validate that the board for this channel is valid and alive */ + if ((mpd == (struct mpdev *)NULL) || (!(mpd->mpd_alive))) + return (-ENODEV); + + dev_dbg(mpd->dev, "eqnx_open: device %d\n", d); + + mpc->mpc_major = major; + mpc->mpc_minor = minor; + + spin_lock_irqsave(&mpd->mpd_lock, flags); + + /* verify that the channel is valid and alive */ + nchan = MPCHAN(d); + if ((mpd >= &eqnx_dev[eqnx_nssps]) || (!(mpd->mpd_alive)) || + (nchan >= (int)mpd->mpd_nchan) || + (mpc->mpc_icp->icp_rng_state != RNG_GOOD) || + (mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_active != DEV_GOOD)) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (-ENODEV); + } + + /* if a bridge, check the mux */ + if (mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_bridge) + if (mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_rmt_active != + DEV_GOOD) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (-ENODEV); + } + + /* Do not allow ports to open on SST-16 boards without a panel */ + if (mpd->mpd_board_def->flags & NOPANEL) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (-ENODEV); + } + + /* check for channel number outside valid range, SSP64 only */ + if (mpd->mpd_board_def->asic == SSP64) { + if ((MPCHAN(d) % 16) >= + mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_chan) { + dev_dbg(mpd->dev, "eqnx_open: mpchan(%d) " + "lmx_no(%d) lmx_chan(%d)\n", + MPCHAN(d), mpc->mpc_lmxno, + mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_chan); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (-ENODEV); + } + } + + mpc->refcount++; + + /* initialize termios struct if required */ + mpc->mpc_tty = tty; + tp = tty; + if (tty->termios == NULL) { + *tty->termios = *mpc->normaltermios; + } + + if (!(mpc->flags & ASYNC_INITIALIZED)) { + tty->driver_data = mpc; + + /* force CLOCAL on for RS422 ports */ + if ((mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_id == LMX_8E_422) || + (mpc->mpc_icp->lmx[mpc->mpc_lmxno].lmx_id == LMX_PM16_422)) + tty->termios->c_cflag |= CLOCAL; + + if ((mpc->mpc_flags & MPC_OPEN) == 0) { + dev_dbg(mpd->dev, "eqnx_open: device %d " + "calling megaparam\n", d); + chanon(mpc); + eqnx_megaparam(d); + } + + mpc->carr_state = eqnx_modem(d, TURNON); + mpc->flags |= ASYNC_INITIALIZED; + clear_bit(TTY_IO_ERROR, &tty->flags); + } + + /* + * If port is in the middle of closing, then wait. Get error status + * from flag settings. + */ + if (mpc->flags & ASYNC_CLOSING) { + dev_dbg(mpd->dev, "eqnx_open: sleeping on close_wait " + "for device %d\n", d); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + wait_event_interruptible(mpc->close_wait, + (mpc->flags & ASYNC_CLOSING) == 0); + if (mpc->flags & ASYNC_HUP_NOTIFY) + return (-EAGAIN); + + return (-ERESTARTSYS); + } + + /* callin device is only one available */ + if (1) { + dev_dbg(mpd->dev, "eqnx_open: opening dialin device %d\n", d); + if (filp->f_flags & O_NONBLOCK) { + dev_dbg(mpd->dev, "eqnx_open: opening dialin device " + "%d non_blocking with c_flags = %o\n", + d, tp->termios->c_cflags); + } else { + mpc->openwaitcnt++; + if (mpc->refcount) + mpc->refcount--; + + while (1) { + mpc->carr_state = eqnx_modem(d, TURNON); + + if (tty_hung_up_p(filp) || + ((mpc->flags & ASYNC_INITIALIZED) == 0)) { + if (mpc->flags & ASYNC_HUP_NOTIFY) + rc = -EBUSY; + else + rc = -ERESTARTSYS; + break; + } + + /* if CLOCAL set, allow the open */ + if (((mpc->flags & ASYNC_CLOSING) == 0) && + ((tp->termios->c_cflag & CLOCAL) || + (mpc->carr_state))) + break; + + if (signal_pending(current)) { + rc = -ERESTARTSYS; + break; + } + + dev_dbg(mpd->dev, "eqnx_open: device %d " + "sleeping on open_wait\n", d); + + /* wait while DCD is low */ + mpc->open_wait_wait++; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + wait_event_interruptible(mpc->open_wait, + mpc->open_wait_wait == + 0); + spin_lock_irqsave(&mpd->mpd_lock, flags); + if (signal_pending(current)) { + rc = -ERESTARTSYS; + break; + } + if (mpc->carr_state) + break; + } + if (!tty_hung_up_p(filp)) + mpc->refcount++; + mpc->openwaitcnt--; + if (rc < 0) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + dev_dbg(mpd->dev, "eqnx_open: device %d " + "open fail w/ c_cflag=%o carr_stat=%d, " + "rc=%d\n", + d, tp->termios->c_cflag, + mpc->carr_state, rc); + return (rc); + } + } + dev_dbg(mpd->dev, "eqnx_open: device %d open fail w/ " + "c_cflag=%o, carr_stat=%d, rc=%d\n", + d, tp->termios->c_cflag, mpc->carr_state, rc); + mpc->flags |= ASYNC_NORMAL_ACTIVE; + } + + if ((mpc->refcount == 1) && (mpc->flags & ASYNC_SPLIT_TERMIOS)) { + *tp->termios = *mpc->normaltermios; + } + + /* SSP4 channel setup */ + if ((mpd->mpd_board_def->asic != SSP64) && (!mpc->xmit_buf)) { + unsigned long page; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + page = get_zeroed_page(GFP_KERNEL); + if (!page) + return (-ENOMEM); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + mpc->xmit_buf = (unsigned char *)page; + mpc->xmit_head = 0; + } + mpc->pgrp = MY_GROUP(); + + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + + return (0); +} + +/* + * eqnx_close(tty, filp) + * + * Close of tty device associated with SST channel. + * Called on last close of any device. + * Release resources, reset device, and wakeup any sleepers + * waiting for channel to become idle. + * + * tty = pointer to tty structure. + * filp = file structure. + */ +void eqnx_close(struct tty_struct *tty, struct file *filp) +{ + struct mpchan *mpc; + struct mpdev *mpd; + int nchan, d; + unsigned long flags; + u16 attn; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + if ((mpd = mpc->mpc_mpd) == (struct mpdev *)NULL) + return; + nchan = MPCHAN(d); + if ((mpd >= &eqnx_dev[eqnx_nssps]) || (!(mpd->mpd_alive)) || + (nchan >= (int)mpd->mpd_nchan)) + return; + + dev_dbg(mpd->dev, "eqnx_close: device %d being closed\n", d); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + if (tty_hung_up_p(filp)) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return; + } + dev_dbg(mpd->dev, "eqnx_close: device %d being closed with " + "refcount = %d\n", d, mpc->refcount); + /* do nothing if not final close */ + if (mpc->refcount) + mpc->refcount -= 1; + if (mpc->refcount) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return; + } + + mpc->flags |= ASYNC_CLOSING; + if (mpc->flags & ASYNC_NORMAL_ACTIVE) + *mpc->normaltermios = *tty->termios; + if (tty == eqnx_txcooktty) + eqnx_flush_chars_locked(tty); + tty->closing = true; + + /* wait for all data to be sent if busy */ + if (mpc->mpc_flags & MPC_BUSY) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + tty_wait_until_sent(tty, (unsigned long)mpc->closing_wait); + spin_lock_irqsave(&mpd->mpd_lock, flags); + } + + mpc->flags &= ~ASYNC_INITIALIZED; + if (tty->termios->c_cflag & HUPCL) { + mpc->flags &= ~ASYNC_INITIALIZED; + eqnx_modem(d, TURNOFF); + } + + mpc->mpc_flags &= ~(MPC_SOFTCAR | MPC_DIALOUT | MPC_MODEM | + MPC_DIALIN | MPC_CTS); + attn = SSTRD16(mpc->mpc_icpi->cin_attn_ena); + attn &= ~ENA_DCD_CNG; + SSTWR16(mpc->mpc_icpi->cin_attn_ena, attn); + mpc->mpc_icpo->cout_cpu_req &= ~TX_SUSP; + set_bit(TTY_IO_ERROR, &tty->flags); + if (tty->ldisc.flush_buffer) + (tty->ldisc.flush_buffer) (tty); + chanoff(mpc); + + eqnx_flush_buffer_locked(tty); + tty->closing = false; + tty->driver_data = (void *)NULL; + mpc->mpc_tty = (struct tty_struct *)NULL; + + /* wakeup open waiters */ + if (mpc->openwaitcnt) { + if (mpc->close_delay) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + delay_jiffies(mpc->close_delay); + spin_lock_irqsave(&mpd->mpd_lock, flags); + } + dev_dbg(mpd->dev, "eqnx_close: waking up open_waiters for " + "device %d\n", d); + mpc->open_wait_wait = 0; + wake_up_interruptible(&mpc->open_wait); + } + + /* SSP channel cleanup */ + if ((mpd->mpd_board_def->asic != SSP64) && (mpc->xmit_buf)) { + free_page((unsigned long)mpc->xmit_buf); + mpc->xmit_buf = NULL; + } + + mpc->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING); + dev_dbg(mpd->dev, "eqnx_close: close complete for device %d\n", d); + wake_up_interruptible(&mpc->close_wait); + eqnx_write_wakeup_deferred = 0; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return; +} + +/* + * eqnx_write(tty, buf, count) + * + * Write data bytes to the tty device. In 2.6 kernel, buffer + * is always in kernel space. + * + * Return with the number of bytes accepted. + * + * tty = pointer to tty structure. + * buf = buffer of bytes to write. + * count = number of bytes to write. + */ +int eqnx_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct mpchan *mpc; + int space, nx, d, qsize, written = 0, c = 0, datascope = 0; + volatile union global_regs_u *icpg; + volatile struct icp_out_struct *icpo; + volatile struct cout_que_struct *icpq; + unsigned char oldreg, *b; + struct mpdev *mpd; + unsigned long flags; + u16 qcnt, attn_enbl; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return (-ENODEV); + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return (-ENODEV); + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return (-ENODEV); + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_write: tty=%d, count=%d\n", + (unsigned int)SSTMINOR(mpc->mpc_major, mpc->mpc_minor), count); + + if (tty == eqnx_txcooktty) + eqnx_flush_chars(tty); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + icpo = mpc->mpc_icpo; + icpg = (volatile union global_regs_u *)icpo; + icpq = &icpo->cout_q0; + + qsize = mpc->mpc_txq.q_size; + space = qsize - (mpc->mpc_count + SSTRD16(icpq->q_data_count)) - 1; + if (!tty->stopped && !tty->hw_stopped && space >= (qsize >> 4)) { + if ((mpd->mpd_board_def->asic != SSP64) && (mpc->xmit_cnt)) + goto ssp_end; + dev_dbg(mpd->dev, "eqnx_write: device = %d, q_size = %d, " + "space = %d\n", + SSTMINOR(mpc->mpc_major, mpc->mpc_minor), + mpc->mpc_txq.q_size, space); + c = MIN(space, count); + b = (unsigned char *)buf; + + mpc->mpc_flags |= MPC_BUSY; + if ((mpc->mpc_flags & MPC_DSCOPEW) && + (eqnx_dscope[1].chan == (mpc - eqnx_chan))) + datascope = 1; + + while (c > 0) { + /* copy data directly from buffer to SSP queue */ + nx = MIN(c, (mpc->mpc_txq.q_end - + mpc->mpc_txq.q_ptr + 1)); + memcpy((mpc->mpc_txq.q_addr + mpc->mpc_txq.q_ptr), + b, nx); + mpc->mpc_txq.q_ptr += nx; + if (mpc->mpc_txq.q_ptr > mpc->mpc_txq.q_end) + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + + /* if datascope enabled, copy to datascope buffers */ + if (datascope) { + int room, move_cnt, d = 1; + unsigned char *ubase = b; + room = DSQSIZE - ((eqnx_dscope[d].next - + eqnx_dscope[d].q.q_ptr) & + (DSQMASK)); + if (nx > room) + eqnx_dscope[d].status |= DSQWRAP; + else + room = nx; + while (room > 0) { + move_cnt = MIN(room, + eqnx_dscope[d].q.q_end - + eqnx_dscope[d].next + 1); + memcpy(eqnx_dscope[d].q.q_addr + + eqnx_dscope[d].next, ubase, + move_cnt); + eqnx_dscope[d].next += move_cnt; + if (eqnx_dscope[d].next > + eqnx_dscope[d].q.q_end) + eqnx_dscope[d].next = + eqnx_dscope[d].q.q_begin; + ubase += move_cnt; + room -= move_cnt; + } + eqnx_dscope[d].scope_wait_wait = 0; + wake_up_interruptible(&eqnx_dscope[d]. + scope_wait); + } + + c -= nx; + count -= nx; + written += nx; + buf += nx; + b += nx; + } + + oldreg = icpo->cout_cpu_req; + icpo->cout_cpu_req |= TX_SUSP; + eqnx_chnl_sync(mpc); + + /* force the send if required */ + qcnt = SSTRD16(icpq->q_data_count); + if (qcnt < 9) { + if ((icpo->cout_intnl_svd_toggls & TOGGLS_LSEND) == + (icpo->cout_cpu_req & CPU_SND_REQ)) { + icpo->cout_cpu_req ^= (CPU_SND_REQ); + mpc->mpc_cout_events &= ~EV_CPU_REQ_DN; + } + } + qcnt += written; + SSTWR16(icpq->q_data_count, qcnt); + space -= written; + dev_dbg(mpd->dev, "eqnx_write: wrote %d chars for device %d\n", + written, SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + dev_dbg(mpd->dev, "eqnx_write: q data count %d, space %d " + "for device %d\n", + qcnt, space, SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + if (!(oldreg & TX_SUSP)) + icpo->cout_cpu_req &= ~TX_SUSP; + mpc->mpc_output += written; + attn_enbl = SSTRD16(icpo->cout_attn_enbl); + attn_enbl |= (ENA_TX_EMPTY_Q0 | ENA_TX_LOW_Q0); + SSTWR16(icpo->cout_attn_enbl, attn_enbl); + } + +ssp_end: + /* all done if SSP64 */ + if (mpd->mpd_board_def->asic == SSP64) { + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + dev_dbg(mpd->dev, "eqnx_write: wrote %d bytes for device %d\n", + written, d); + return (written); + } + + /* now try to fill xmit_buf with the data */ + while (count) { + dev_dbg(mpd->dev, "eqnx_write: fill xmit_buf with %d\n", count); + c = MIN(count, MIN(XMIT_BUF_SIZE - mpc->xmit_cnt - 1, + XMIT_BUF_SIZE - mpc->xmit_head)); + if (c <= 0) + break; + b = (unsigned char *)buf; + + if (mpc->xmit_buf == NULL) + break; + memcpy(mpc->xmit_buf + mpc->xmit_head, b, c); + mpc->xmit_head = (mpc->xmit_head + c) & (XMIT_BUF_SIZE - 1); + mpc->xmit_cnt += c; + buf += c; + count -= c; + written += c; + dev_dbg(mpd->dev, "eqnx_write: filled xmit_buf for device " + "%d count %d\n", d, c); + } + + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + dev_dbg(mpd->dev, "eqnx_write: wrote %d bytes for device %d\n", + written, d); + + return (written); +} + +/* + * eqnx_put_char(tty, ch) + * + * Write single data byte to the tty device. Note data is queued and + * flush call must be done to force to the device. + * + * tty = pointer to tty structure. + * ch = byte to write. + */ +void eqnx_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned long flags; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + dev_dbg(mpd->dev, "eqnx_put_char: tty=%d, ch=%x\n", d, (int)ch); + + /* allocate tkcooktty buffer for queueing put_char bytes */ + if (tty != eqnx_txcooktty) { + if (eqnx_txcooktty != (struct tty_struct *)NULL) + eqnx_flush_chars(eqnx_txcooktty); + eqnx_txcooktty = tty; + } + + if (mpd->mpd_board_def->asic != SSP64) { + /* SSP4 */ + spin_lock_irqsave(&mpd->mpd_lock, flags); + if (mpc->xmit_head >= (XMIT_BUF_SIZE - 1)) { + eqnx_flush_chars_locked(tty); + eqnx_txcooktty = tty; + } + if (mpc->xmit_buf != NULL) { + mpc->xmit_buf[mpc->xmit_head++] = ch; + mpc->xmit_head &= (XMIT_BUF_SIZE - 1); + mpc->xmit_cnt++; + } + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + + if (eqnx_write_wakeup_deferred) { + eqnx_write_wakeup_deferred = 0; + tty->ldisc.write_wakeup(tty); + } + } else { + /* SSP64 */ + if (eqnx_txcooksize >= (XMIT_BUF_SIZE - 1)) { + eqnx_flush_chars(eqnx_txcooktty); + eqnx_txcooktty = tty; + } + eqnx_txcookbuf[eqnx_txcooksize++] = ch; + } +} + +/* + * eqnx_flush_chars_locked(tty) + * + * Flush (actually drain) data bytes queued by put_char routine. + * Helper routine called by eqnx_flush_chars. + * + * tty = pointer to tty structure. + * + * mpdev (board-level) lock ** MUST ** be held. + */ +static void eqnx_flush_chars_locked(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_flush_chars_locked: tty=%d\n", d); + + if (mpd == (struct mpdev *)NULL) + return; + + if (tty == (struct tty_struct *)NULL) + return; + if (mpc == (struct mpchan *)NULL) + return; + + if (eqnx_txcooktty == NULL) + return; + else { + +#ifdef DEBUG_LOCKS + if (!(spin_is_locked(&mpd->mpd_lock))) + dev_dbg(mpd->dev, "eqnx_flush_chars_locked: mpd " + "lock !locked\n"); +#endif + + if (mpd->mpd_board_def->asic == SSP64) { + /* SSP64 */ + unsigned int cooksize = eqnx_txcooksize; + eqnx_txcooksize = 0; + eqnx_txcookrealsize = 0; + eqnx_txcooktty = (struct tty_struct *)NULL; + if (cooksize == 0) + return; + dev_dbg(mpd->dev, "eqnx_flush_chars_locked: " + "calling eqnx_write_SSP64\n"); + eqnx_write_SSP64(mpc, eqnx_txcookbuf, cooksize); + } else { + /* SSP4 */ + if (mpc->xmit_cnt == 0) + return; + dev_dbg(mpd->dev, "eqnx_flush_chars_locked: " + "calling eqnx_write_SSP4\n"); + eqnx_write_SSP4(mpc, EQNX_COOKED); + } + } +} + +/* + * eqnx_flush_chars(tty) + * + * Flush (actually drain) data bytes queued by put_char routine. + * + * tty = pointer to tty structure. + * + * mpdev (board-level) lock ** MUST NOT ** be held. + */ +void eqnx_flush_chars(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned int d; + unsigned long flags; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + if (eqnx_txcooktty == NULL) + return; + else { + +#ifdef DEBUG_LOCKS + if (spin_is_locked(&mpd->mpd_lock)) + dev_dbg(mpd->dev, "eqnx_flush_chars: mpd lock " + "already held\n"); +#endif + + spin_lock_irqsave(&mpd->mpd_lock, flags); + eqnx_flush_chars_locked(tty); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + + if (eqnx_write_wakeup_deferred) { + eqnx_write_wakeup_deferred = 0; + tty->ldisc.write_wakeup(tty); + } + } +} + +/* + * eqnx_write_room(tty) + * + * Return amount of room available for a write. + * + * tty = pointer to tty structure. + */ +int eqnx_write_room(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + volatile struct cout_que_struct *icpq; + volatile struct icp_out_struct *icpo; + unsigned int space; + int d; + unsigned long flags; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return (-ENODEV); + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return (-ENODEV); + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return (-ENODEV); + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return (-ENODEV); + + dev_dbg(mpd->dev, "eqnx_write_room: device = %d\n", d); + spin_lock_irqsave(&mpd->mpd_lock, flags); + + if (mpd->mpd_board_def->asic != SSP64) { + /* SSP4 */ + int ret; + ret = (XMIT_BUF_SIZE - mpc->xmit_cnt - 1); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + if (ret < 0) + return (0); + return (ret); + } + + if (tty == eqnx_txcooktty) { + if (eqnx_txcookrealsize != 0) { + space = (eqnx_txcookrealsize - eqnx_txcooksize); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (space); + } + } + + icpo = mpc->mpc_icpo; + icpq = &icpo->cout_q0; + space = mpc->mpc_txq.q_size - (mpc->mpc_count + + SSTRD16(icpq->q_data_count)) - 1; + if (tty == eqnx_txcooktty) { + eqnx_txcookrealsize = space; + space -= eqnx_txcooksize; + } + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + return (space); +} + +/* + * eqnx_chars_in_buffer(tty) + * + * Return number of data bytes buffered to be output + * + * tty = pointer to tty structure. + */ +int eqnx_chars_in_buffer(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned long flags; + volatile struct cout_que_struct *icpq; + volatile struct icp_out_struct *icpo; + unsigned int count; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return (-ENODEV); + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return (-ENODEV); + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return (-ENODEV); + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return (-ENODEV); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + icpo = mpc->mpc_icpo; + icpq = &icpo->cout_q0; + count = SSTRD16(icpq->q_data_count); + + /* + * for SSP4, include include internal buffer in addition + * to number of data bytes queued to the SSP + */ + if (mpc->mpc_mpd->mpd_board_def->asic != SSP64) + count += mpc->xmit_cnt; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + dev_dbg(mpd->dev, "eqnx_chars_in_buffer: count for device %d = " + "%d\n", d, count); + return (count); +} + +/* + * eqnx_throttle(tty) + * + * Stop queuing input to the line discipline. + * + * tty = pointer to tty structure. + */ +void eqnx_throttle(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_throttle: device %d\n", + SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + mpc->mpc_block = true; +} + +/* + * eqnx_unthrottle(tty) + * + * Resume queuing input to the line discipline. + * + * tty = pointer to tty structure. + */ +void eqnx_unthrottle(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_unthrottle: device %d\n", + SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + mpc->mpc_block = false; +} + +/* + * eqnx_flush_buffer_locked(tty) + * + * Flush all input and output queued data. + * Helper routine called by eqnx_flush_buffer. + * + * tty = pointer to tty structure. + * + * mpdev (board-level) lock ** MUST ** be held + */ +void eqnx_flush_buffer_locked(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + volatile struct icp_in_struct *icpi; + volatile struct icp_out_struct *icpo; + volatile struct cout_que_struct *icpq; + volatile struct cin_bnk_struct *icpb; + u8 cin; + u16 nxt_dma; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + +#ifdef DEBUG_LOCKS + if (!(spin_is_locked(&mpd->mpd_lock))) + dev_dbg(mpd->dev, "eqnx_flush_buffer_locked: mpd lock not " + "locked\n"); +#endif + + icpi = mpc->mpc_icpi; + icpo = mpc->mpc_icpo; + icpq = &icpo->cout_q0; + + dev_dbg(mpd->dev, "eqnx_flush_buffer_locked: device = %d\n", d); + if (tty == eqnx_txcooktty) { + eqnx_txcooktty = (struct tty_struct *)NULL; + eqnx_txcooksize = 0; + eqnx_txcookrealsize = 0; + } + + /* flush transmit buffers */ + icpo->cout_lck_cntrl |= LCK_Q_ACT; + eqnx_chnl_sync(mpc); + icpo->cout_dma_stat_save = 0; + icpo->cout_intnl_fifo_ptrs = 0; + SSTWR16(icpq->q_data_count, 0); + mpc->mpc_count = 0; + SSTWR16(icpq->q_data_ptr_l, (mpc->mpc_txq.q_begin + mpc->mpc_txbase)); + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + mpc->xmit_cnt = mpc->xmit_head = mpc->xmit_tail = 0; + SSTWR16(icpo->cout_attn_enbl, EN_REG_UPDT_EV); + icpo->cout_lck_cntrl &= ~LCK_Q_ACT; + mpc->mpc_flags &= ~MPC_BUSY; + + /* flush receiver queue */ + /* set rx ptr = tail */ + icpb = (icpi->cin_locks & LOCK_A) ? &icpi->cin_bank_b : + &icpi->cin_bank_a; + /* wait for valid registers */ + if (!(SSTRD16(icpb->bank_events) & EV_REG_UPDT)) + eqnx_frame_wait(mpc, 2); + cin = icpi->cin_locks; + icpi->cin_locks = cin | (DIS_BANK_A | DIS_BANK_B); + eqnx_chnl_sync(mpc); + icpi->cin_bank_a.bank_fifo_lvl &= ~0x8f; + icpi->cin_bank_b.bank_fifo_lvl &= ~0x8f; + nxt_dma = SSTRD16(icpb->bank_nxt_dma); + SSTWR16(icpi->cin_tail_ptr_a, nxt_dma); + SSTWR16(icpi->cin_tail_ptr_b, nxt_dma); + mpc->mpc_rxq.q_ptr = nxt_dma; + icpi->cin_locks = cin; + mpc->mpc_flags |= MPC_RXFLUSH; + mpc->mpc_block = false; + wake_up_interruptible(&tty->write_wait); + + /* signal write wakeup when safe to call ldisc */ + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + eqnx_write_wakeup_deferred++; +} + +/* + * eqnx_flush_buffer(tty) + * + * Flush all input and output queued data. + * + * tty = pointer to tty structure. + * + * mpdev (board-level) lock ** MUST NOT ** be held + */ +void eqnx_flush_buffer(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned long flags; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + +#ifdef DEBUG_LOCKS + if (spin_is_locked(&mpd->mpd_lock)) + dev_dbg(mpd->dev, "eqnx_flush_buffer: mpd lock already " + "held\n"); +#endif + spin_lock_irqsave(&mpd->mpd_lock, flags); + eqnx_flush_buffer_locked(tty); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + + if (eqnx_write_wakeup_deferred) { + eqnx_write_wakeup_deferred = 0; + tty->ldisc.write_wakeup(tty); + } +} + +/* + * eqnx_stop(tty) + * + * Stop queueing data bytes to be output. + * + * tty = pointer to tty structure. + */ +void eqnx_stop(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + volatile struct icp_out_struct *icpo; + unsigned long flags; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + icpo = mpc->mpc_icpo; + dev_dbg(mpd->dev, "eqnx_stop: device %d\n", d); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + + /* suspend output */ + icpo->cout_cpu_req |= CPU_PAUSE; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); +} + +/* + * eqnx_start(tty) + * + * Re-start queueing data bytes to be output. + * + * tty = pointer to tty structure. + */ +void eqnx_start(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + volatile struct icp_out_struct *icpo; + unsigned long flags; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + icpo = mpc->mpc_icpo; + dev_dbg(mpd->dev, "eqnx_start: device %d\n", d); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + + /* resume output */ + icpo->cout_cpu_req &= ~CPU_PAUSE; + spin_unlock_irqrestore(&mpd->mpd_lock, flags); +} + +/* + * eqnx_hangup(tty) + * + * Hangup tty device. + * Drop control signals, flush buffer, and close port. + * + * tty = pointer to tty structure. + */ +void eqnx_hangup(struct tty_struct *tty) +{ + struct mpchan *mpc; + struct mpdev *mpd; + unsigned long flags; + int d; + u16 attn; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + dev_dbg(mpd->dev, "eqnx_hangup: device %d\n", d); + + spin_lock_irqsave(&mpd->mpd_lock, flags); + + mpc->flags &= ~ASYNC_INITIALIZED; + if (mpc->flags & ASYNC_NORMAL_ACTIVE) + *mpc->normaltermios = *tty->termios; + if (tty == eqnx_txcooktty) + eqnx_flush_chars_locked(tty); + if (tty->termios->c_cflag & HUPCL) + eqnx_modem(d, TURNOFF); + + mpc->mpc_flags &= ~(MPC_SOFTCAR | MPC_DIALOUT | MPC_MODEM | + MPC_DIALIN | MPC_CTS); + attn = SSTRD16(mpc->mpc_icpi->cin_attn_ena); + attn &= ~ENA_DCD_CNG; + SSTWR16(mpc->mpc_icpi->cin_attn_ena, attn); + chanoff(mpc); + eqnx_flush_buffer_locked(tty); + set_bit(TTY_IO_ERROR, &tty->flags); + mpc->flags &= ~ASYNC_NORMAL_ACTIVE; + mpc->mpc_tty = (struct tty_struct *)NULL; + mpc->refcount = 0; + + /* reset SSP4 xmit buf variables */ + if ((mpd->mpd_board_def->asic != SSP64) && (mpc->xmit_buf)) { + free_page((unsigned long)mpc->xmit_buf); + mpc->xmit_buf = NULL; + mpc->xmit_cnt = 0; + mpc->xmit_head = 0; + mpc->xmit_tail = 0; + } + mpc->open_wait_wait = 0; + wake_up_interruptible(&mpc->open_wait); + spin_unlock_irqrestore(&mpd->mpd_lock, flags); + + if (eqnx_write_wakeup_deferred) { + eqnx_write_wakeup_deferred = 0; + tty->ldisc.write_wakeup(tty); + } +} + +/* + * eqnx_set_termios(tty, old) + * + * Device termios structure has been modified. + * + * tty = pointer to tty structure. + * old = previous termios settings. + */ +void eqnx_set_termios(struct tty_struct *tty, struct termios *old) +{ + struct mpchan *mpc; + struct mpdev *mpd; + int d; + struct termios *tiosp; + unsigned long flags; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return; + + mpd = mpc->mpc_mpd; + if (mpd == (struct mpdev *)NULL) + return; + + dev_dbg(mpd->dev, "eqnx_set_termios: device %d\n", d); + + tiosp = tty->termios; + if ((tiosp->c_cflag == old->c_cflag) && + (tiosp->c_iflag == old->c_iflag) && + (tiosp->c_cc[VSTOP] == old->c_cc[VSTOP]) && + (tiosp->c_cc[VSTART] == old->c_cc[VSTART])) + return; + + spin_lock_irqsave(&mpd->mpd_lock, flags); + dev_dbg(mpd->dev, "eqnx_set_termios: calling megaparam dev %d\n", d); + eqnx_megaparam(d); + if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0)) + tty->hw_stopped = 0; + if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL)) { + mpc->carr_state = true; + mpc->open_wait_wait = 0; + wake_up_interruptible(&mpc->open_wait); + } + spin_unlock_irqrestore(&mpd->mpd_lock, flags); +} + +/* + * eqnx_tiocmget(tty, file) + * + * return state of control signals. + * + * tty = pointer to tty structure. + * file = pointer to file structure. + */ +int eqnx_tiocmget(struct tty_struct *tty, struct file *file) +{ + u32 result = 0; + struct mpchan *mpc; + struct mpdev *mpd; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return -ENODEV; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return -ENODEV; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return -ENODEV; + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_tiocmget: device %d\n", d); + + result = eqnx_get_modem_info(mpc); + return result; +} + +/* + * eqnx_tiocmset(tty, file, set, clear) + * + * set state of control signals. + * + * tty = pointer to tty structure. + * file = pointer to file structure. + * set = control signals to set. + * clear = control signals to clear. + */ +int eqnx_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + struct mpchan *mpc; + struct mpdev *mpd; + int d; + + /* channel validity checks */ + if (tty == (struct tty_struct *)NULL) + return -ENODEV; + mpc = (struct mpchan *)tty->driver_data; + if (mpc == (struct mpchan *)NULL) + return -ENODEV; + + d = SSTMINOR(mpc->mpc_major, mpc->mpc_minor); + if (d > (eqnx_nssps * MAXCHNL_BRD)) + return -ENODEV; + + mpd = mpc->mpc_mpd; + dev_dbg(mpd->dev, "eqnx_tiocmset: device %d, set = %x, clear = %x\n", + d, set, clear); + + eqnx_set_modem_info(mpc, TIOCMBIS, set, tty); + eqnx_set_modem_info(mpc, TIOCMBIC, clear, tty); + return 0; +} + +/* + * chan_alive(index) + * + * return true if channel alive + */ +static inline int chan_alive(int index) +{ + int chan; + struct mpchan *mpc; + struct mpdev *mpd; + struct icp_struct *icp; + volatile struct icp_in_struct *icpi; + volatile struct cin_bnk_struct *icpb; + + chan = index % MAXCHNL_BRD; + mpc = &eqnx_chan[index]; + if (mpc == NULL) + return false; + + mpd = mpc->mpc_mpd; + if ((mpd == NULL) || (chan > mpd->mpd_nchan)) + return false; + + if (!(mpd->mpd_alive)) + return false; + + icp = &mpd->icp[chan / mpd->mpd_sspchan]; + if (icp->icp_rng_state != RNG_GOOD) + return false; + + if (mpd->mpd_board_def->asic == SSP64) { + icpi = mpc->mpc_icpi; + icpb = (icpi->cin_locks & LOCK_A) ? &icpi->cin_bank_b : + &icpi->cin_bank_a; + if ((!(SSTRD16(icpb->bank_signals) & LMX_PRESENT)) || + (icp->lmx[mpc->mpc_lmxno].lmx_active != DEV_GOOD)) + return false; + } + + return true; +} + +/* + * chanon(mpc) + * + * Turn on a channel. + * + * mpc = pointer to channel structure. + * + * mpdev (board-level) lock ** MUST ** be held. + */ +static void chanon(struct mpchan *mpc) +{ + volatile struct icp_in_struct *icpi = mpc->mpc_icpi; + volatile struct icp_out_struct *icpo = mpc->mpc_icpo; + volatile union global_regs_u *icpg; + volatile struct cout_que_struct *icpq = &icpo->cout_q0; + volatile struct cin_bnk_struct *icpb; + unsigned short cur; + u8 cin; + u16 nxt_dma, attn_ena, status, attn_enbl; +#ifdef DEBUG_LOCKS + struct mpdev *mpd = mpc->mpc_mpd; + + if (!(spin_is_locked(&mpc->mpc_mpd->mpd_lock))) + dev_dbg(mpd->dev, "chanon: mpd lock !locked\n"); +#endif + + if (mpc->mpc_mpd->mpd_board_def->asic == SSP64) + icpg = (volatile union global_regs_u *)icpo; + else + icpg = (volatile union global_regs_u *) + ((unsigned long)(mpc->mpc_icpi) + 0x400); + + icpb = (icpi->cin_locks & LOCK_A) ? &icpi->cin_bank_b : + &icpi->cin_bank_a; + /* make sure registers are valid */ + if (!(SSTRD16(icpb->bank_events) & EV_REG_UPDT)) + eqnx_frame_wait(mpc, 2); + + if ((SSTRD16(icpb->bank_signals) & (LMX_ONLN | LMX_PRESENT | + AMI_CNFG)) == + (LMX_ONLN | LMX_PRESENT | AMI_CNFG)) { + /* if bridge, stop output on MUX disconnect */ + icpi->cin_susp_output_lmx |= MUX_NOT_CONN; + icpo->cout_lmx_command |= TX_TRGT_MUX; + } else { + /* local panel */ + icpi->cin_susp_output_lmx &= ~MUX_NOT_CONN; + icpo->cout_lmx_command &= ~TX_TRGT_MUX; + } + + /* lock output */ + icpo->cout_lck_cntrl |= LCK_Q_ACT; + + /* lock input */ + cin = icpi->cin_locks; + icpi->cin_locks = cin | DIS_BANK_A | DIS_BANK_B; + eqnx_chnl_sync(mpc); + + icpi->cin_bank_a.bank_fifo_lvl &= ~0x8f; + icpi->cin_bank_b.bank_fifo_lvl &= ~0x8f; + nxt_dma = SSTRD16(icpb->bank_nxt_dma); + SSTWR16(icpi->cin_tail_ptr_a, nxt_dma); + SSTWR16(icpi->cin_tail_ptr_b, nxt_dma); + mpc->mpc_rxq.q_ptr = nxt_dma; + SSTWR16(icpi->cin_min_char_lvl, 1); + + /* unlock input */ + attn_ena = SSTRD16(icpi->cin_attn_ena); + attn_ena |= (ENA_DMA_FAIL | ENA_IXOFF_SVS | ENA_VMIN); + SSTWR16(icpi->cin_attn_ena, attn_ena); + icpi->cin_locks = cin; + icpo->cout_dma_stat_save = 0; + icpo->cout_intnl_fifo_ptrs = 0; + SSTWR16(icpq->q_data_count, 0); + SSTWR16(icpq->q_data_ptr_l, (mpc->mpc_txbase + mpc->mpc_txq.q_begin)); + icpi->cin_iband_flow_cntrl = 0; + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + mpc->mpc_count = 0; + status = SSTRD16(icpo->cout_status); + status &= ~TXSR_SND_BRK; + SSTWR16(icpo->cout_status, status); + GET_CTRL_SIGS(mpc, cur); + cur |= TX_HFC_DTR | TX_HFC_RTS; + SET_CTRL_SIGS(mpc, cur); + + /* unlock output */ + attn_enbl = SSTRD16(icpo->cout_attn_enbl); + attn_enbl |= EN_REG_UPDT_EV; + SSTWR16(icpo->cout_attn_enbl, attn_enbl); + icpo->cout_lck_cntrl &= ~LCK_Q_ACT; + + /* calibrate invent char count */ + mpc->mpc_tx_last_invent = SSTRD16(icpo->cout_ses_char_ctr_a); + + /* Calibrate events and control signals in both banks */ + FREEZ_BANK(mpc); + FREEZ_BANK(mpc); + mpc->mpc_cin_events = 0; + mpc->mpc_flags |= MPC_OPEN; +} + +/* + * chanoff(mpc) + * + * Turn off a channel. + * + * mpc = pointer to channel structure. + * + * mpdev (board-level) lock ** MUST ** be held. + */ +static void chanoff(struct mpchan *mpc) +{ + volatile struct icp_in_struct *icpi; + volatile struct icp_out_struct *icpo; + volatile struct cout_que_struct *icpq; + volatile struct cin_bnk_struct *icpb; + int loop, ok; + u8 cin; + u16 events, nxt_dma, attn_ena; + +#ifdef DEBUG_LOCKS + if (!(spin_is_locked(&mpc->mpc_mpd->mpd_lock))) + dev_dbg(mpc->mpc_mpd->dev, "chanoff: mpd lock !locked\n"); +#endif + + icpi = mpc->mpc_icpi; + icpo = mpc->mpc_icpo; + icpq = &icpo->cout_q0; + icpb = (icpi->cin_locks & LOCK_A) ? &icpi->cin_bank_b : + &icpi->cin_bank_a; + /* make sure registers are valid */ + if (!(SSTRD16(icpb->bank_events) & EV_REG_UPDT)) + eqnx_frame_wait(mpc, 2); + + mpc->mpc_param &= (IOCTLLB | IOCTCTS | IOCTLCK | IOCTRTS); + + /* disable events */ + attn_ena = SSTRD16(icpi->cin_attn_ena); + attn_ena &= (ENA_DCD_CNG | ENA_CTS_CNG | ENA_DSR_CNG | ENA_RI_CNG); + if (!(mpc->mpc_chan % 16)) + attn_ena |= ENA_LMX_CNG; + SSTWR16(icpi->cin_attn_ena, attn_ena); + + /* lock output */ + icpo->cout_lck_cntrl |= LCK_Q_ACT; + eqnx_chnl_sync(mpc); + icpo->cout_intnl_fifo_ptrs = 0; + icpo->cout_dma_stat_save = 0; + SSTWR16(icpq->q_data_count, 0); + SSTWR16(icpq->q_data_ptr_l, (mpc->mpc_txbase + mpc->mpc_txq.q_begin)); + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + mpc->mpc_count = 0; + mpc->carr_state = false; + + /* unlock output and update */ + icpo->cout_lck_cntrl &= ~LCK_Q_ACT; + eqnx_chnl_sync(mpc); + if ((icpo->cout_intnl_svd_toggls & TOGGLS_LSEND) == + (icpo->cout_cpu_req & CPU_SND_REQ)) + icpo->cout_cpu_req ^= CPU_SND_REQ; + + /* wait for ack */ + loop = 0; + ok = false; + while (++loop < 100000) { + if (SSTRD16(icpo->cout_status) & TXSR_EV_B_ACT) + events = SSTRD16(icpo->cout_events_b); + else + events = SSTRD16(icpo->cout_events_a); + if (events & EV_CPU_REQ_DN) { + ok = true; + break; + } + } + + if (!ok) + dev_warn(mpc->mpc_mpd->dev, "chanoff: cpu_req ack failed.\n"); + + /* lock input */ + cin = icpi->cin_locks; + icpi->cin_locks = cin | DIS_BANK_A | DIS_BANK_B; + eqnx_chnl_sync(mpc); + icpi->cin_bank_a.bank_fifo_lvl &= ~0x8f; + icpi->cin_bank_b.bank_fifo_lvl &= ~0x8f; + nxt_dma = SSTRD16(icpb->bank_nxt_dma); + SSTWR16(icpi->cin_tail_ptr_a, nxt_dma); + SSTWR16(icpi->cin_tail_ptr_b, nxt_dma); + mpc->mpc_rxq.q_ptr = nxt_dma; + SSTWR16(icpi->cin_min_char_lvl, 1); + + /* allow overrun check while closed */ + attn_ena = SSTRD16(icpi->cin_attn_ena); + attn_ena |= (ENA_VMIN | ENA_DMA_FAIL); + SSTWR16(icpi->cin_attn_ena, attn_ena); + + /* unlock input */ + icpi->cin_locks = cin; + if (icpi->cin_iband_flow_cntrl & XOFF_RCV) { + icpi->cin_locks |= DIS_IBAND_FLW; + eqnx_chnl_sync(mpc); + icpi->cin_iband_flow_cntrl &= ~XOFF_RCV; + icpi->cin_locks &= ~DIS_IBAND_FLW; + } + + SSTWR16(icpo->cout_attn_enbl, EN_REG_UPDT_EV); + mpc->mpc_flags &= ~MPC_OPEN; + mpc->mpc_cin_events = 0; + mpc->mpc_cout_events = 0; +} + +/********************************************************************** *** + * + * Output support routines + * + ************************************************************************ */ + +/* + * eqnx_write_SSP64(mpc, buf, count) + * write output data to channel. + * + * mpc = pointer to channel structure. + * buf = data buffer. + * count= count of bytes to write. + * + * mpdev (board-level) lock ** MUST ** be held. + */ +static void eqnx_write_SSP64(struct mpchan *mpc, unsigned char *buf, int count) +{ + int c, nx, datascope = 0; + volatile struct icp_out_struct *icpo; + volatile union global_regs_u *icpg; + volatile struct cout_que_struct *icpq; + unsigned char oldreg; + u16 qcnt, attn_enbl; + struct mpdev *mpd = mpc->mpc_mpd; + +#ifdef DEBUG_LOCKS + if (!(spin_is_locked(&mpc->mpc_mpd->mpd_lock))) + dev_dbg(mpd->dev, "eqnx_write_SSP64: mpd lock locked\n"); +#endif + + icpo = mpc->mpc_icpo; + icpg = (volatile union global_regs_u *)icpo; + icpq = &icpo->cout_q0; + + c = count; + mpc->mpc_flags |= MPC_BUSY; + if ((mpc->mpc_flags & MPC_DSCOPEW) && + (eqnx_dscope[1].chan == mpc - eqnx_chan)) + datascope = 1; + + /* direct copy to on-board buffers. */ + nx = MIN(c, (mpc->mpc_txq.q_end - mpc->mpc_txq.q_ptr + 1)); + memcpy((mpc->mpc_txq.q_addr + mpc->mpc_txq.q_ptr), buf, nx); + mpc->mpc_txq.q_ptr += nx; + if (mpc->mpc_txq.q_ptr > mpc->mpc_txq.q_end) + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + if (datascope) { + int room, move_cnt; + int d = 1; + char *ubase = buf; + room = DSQSIZE - ((eqnx_dscope[d].next - + eqnx_dscope[d].q.q_ptr) & (DSQMASK)); + if (nx > room) + eqnx_dscope[d].status |= DSQWRAP; + else + room = nx; + while (room > 0) { + move_cnt = MIN(room, + eqnx_dscope[d].q.q_end - + eqnx_dscope[d].next + 1); + memcpy(eqnx_dscope[d].q.q_addr + eqnx_dscope[d].next, + ubase, move_cnt); + eqnx_dscope[d].next += move_cnt; + if (eqnx_dscope[d].next > eqnx_dscope[d].q.q_end) + eqnx_dscope[d].next = eqnx_dscope[d].q.q_begin; + ubase += move_cnt; + room -= move_cnt; + } + eqnx_dscope[d].scope_wait_wait = 0; + wake_up_interruptible(&eqnx_dscope[d].scope_wait); + } + + buf += nx; + count -= nx; + c -= nx; + if (c) { + memcpy(mpc->mpc_txq.q_addr + mpc->mpc_txq.q_ptr, buf, c); + mpc->mpc_txq.q_ptr += c; + if (mpc->mpc_txq.q_ptr > mpc->mpc_txq.q_end) + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + if (datascope) { + int room, move_cnt; + int d = 1; + char *ubase = buf; + room = DSQSIZE - ((eqnx_dscope[d].next - + eqnx_dscope[d].q.q_ptr) & (DSQMASK)); + if (c > room) + eqnx_dscope[d].status |= DSQWRAP; + else + room = c; + while (room > 0) { + move_cnt = MIN(room, eqnx_dscope[d].q.q_end - + eqnx_dscope[d].next + 1); + memcpy(eqnx_dscope[d].q.q_addr + + eqnx_dscope[d].next, ubase, move_cnt); + eqnx_dscope[d].next += move_cnt; + if (eqnx_dscope[d].next > + eqnx_dscope[d].q.q_end) + eqnx_dscope[d].next = + eqnx_dscope[d].q.q_begin; + ubase += move_cnt; + room -= move_cnt; + } + eqnx_dscope[d].scope_wait_wait = 0; + wake_up_interruptible(&eqnx_dscope[d].scope_wait); + } + buf += c; + count -= c; + } + oldreg = icpo->cout_cpu_req; + icpo->cout_cpu_req |= TX_SUSP; + eqnx_chnl_sync(mpc); + qcnt = SSTRD16(icpq->q_data_count); + if (qcnt < 9) { + if ((icpo->cout_intnl_svd_toggls & 0x4) == + (icpo->cout_cpu_req & 0x4)) { + icpo->cout_cpu_req ^= (CPU_SND_REQ); + mpc->mpc_cout_events &= ~EV_CPU_REQ_DN; + } + } + qcnt += (c + nx); + SSTWR16(icpq->q_data_count, qcnt); + dev_dbg(mpd->dev, "eqnx_write_SSP64: wrote %d chars for device %d\n", + (c + nx), SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + if (!(oldreg & TX_SUSP)) + icpo->cout_cpu_req &= ~TX_SUSP; + mpc->mpc_output += (c + nx); + attn_enbl = SSTRD16(icpo->cout_attn_enbl); + attn_enbl |= (ENA_TX_EMPTY_Q0 | ENA_TX_LOW_Q0); + SSTWR16(icpo->cout_attn_enbl, attn_enbl); +} + +/* + * eqnx_write_SSP4(mpc, func_type) + * write output data to channel. + * + * mpc = pointer to channel structure. + * func_type: one of EQNX_COOKED or EQNX_TXINT + * + * mpdev (board-level) lock ** MUST ** be held. + */ +void eqnx_write_SSP4(struct mpchan *mpc, int func_type) +{ + struct tty_struct *tty = mpc->mpc_tty; + int space, c, nx, datascope = 0, block_count; + volatile struct icp_out_struct *icpo; + volatile union global_regs_u *icpg; + volatile struct cout_que_struct *icpq; + unsigned char oldreg; + u16 qcnt, attn_enbl; + struct mpdev *mpd = mpc->mpc_mpd; + +#ifdef DEBUG_LOCKS + if (!(spin_is_locked(&mpc->mpc_mpd->mpd_lock))) + dev_dbg(mpd->dev, "eqnx_write_SSP4: mpd lock !locked\n"); +#endif + + icpo = mpc->mpc_icpo; + icpg = (volatile union global_regs_u *)icpo; + icpq = &icpo->cout_q0; + + block_count = icpq->q_block_count * 64; + space = mpc->mpc_txq.q_size - (mpc->mpc_count + + SSTRD16(icpq->q_data_count)) - 1; + if ((func_type != EQNX_TXINT) && + ((space < block_count) && (mpc->xmit_cnt >= block_count))) + return; + + while (1) { + c = MIN(space, MIN(mpc->xmit_cnt, + XMIT_BUF_SIZE - mpc->xmit_tail)); + if (c <= 0) + break; + mpc->mpc_flags |= MPC_BUSY; + if ((mpc->mpc_flags & MPC_DSCOPEW) && + (eqnx_dscope[1].chan == mpc - eqnx_chan)) + datascope = 1; + + /* direct copy to on-board buffers. */ + if (mpc->xmit_buf == NULL) + break; + + nx = MIN(c, (mpc->mpc_txq.q_end - mpc->mpc_txq.q_ptr + 1)); + memcpy((mpc->mpc_txq.q_addr + mpc->mpc_txq.q_ptr), + mpc->xmit_buf + mpc->xmit_tail, nx); + mpc->mpc_txq.q_ptr += nx; + if (mpc->mpc_txq.q_ptr > mpc->mpc_txq.q_end) + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + if (datascope) { + int room, move_cnt; + int d = 1; + char *ubase = mpc->xmit_buf + mpc->xmit_tail; + room = DSQSIZE - ((eqnx_dscope[d].next - + eqnx_dscope[d].q.q_ptr) + & (DSQMASK)); + if (nx > room) + eqnx_dscope[d].status |= DSQWRAP; + else + room = nx; + while (room > 0) { + move_cnt = MIN(room, + eqnx_dscope[d].q.q_end - + eqnx_dscope[d].next + 1); + memcpy(eqnx_dscope[d].q.q_addr + + eqnx_dscope[d].next, ubase, move_cnt); + eqnx_dscope[d].next += move_cnt; + if (eqnx_dscope[d].next > + eqnx_dscope[d].q.q_end) + eqnx_dscope[d].next = + eqnx_dscope[d].q.q_begin; + ubase += move_cnt; + room -= move_cnt; + } + eqnx_dscope[d].scope_wait_wait = 0; + wake_up_interruptible(&eqnx_dscope[d].scope_wait); + } + mpc->xmit_tail += nx; + mpc->xmit_tail &= XMIT_BUF_SIZE - 1; + mpc->xmit_cnt -= nx; + c -= nx; + if (mpc->xmit_buf == NULL) + break; + + if (c) { + memcpy(mpc->mpc_txq.q_addr + mpc->mpc_txq.q_ptr, + mpc->xmit_buf + mpc->xmit_tail, c); + mpc->mpc_txq.q_ptr += c; + if (mpc->mpc_txq.q_ptr > mpc->mpc_txq.q_end) + mpc->mpc_txq.q_ptr = mpc->mpc_txq.q_begin; + if (datascope) { + int room, move_cnt; + int d = 1; + char *ubase = mpc->xmit_buf + mpc->xmit_tail; + room = DSQSIZE - ((eqnx_dscope[d].next - + eqnx_dscope[d].q.q_ptr) & + (DSQMASK)); + if (c > room) + eqnx_dscope[d].status |= DSQWRAP; + else + room = c; + while (room > 0) { + move_cnt = MIN(room, + eqnx_dscope[d].q.q_end - + eqnx_dscope[d].next + 1); + memcpy(eqnx_dscope[d].q.q_addr + + eqnx_dscope[d].next, ubase, + move_cnt); + eqnx_dscope[d].next += move_cnt; + if (eqnx_dscope[d].next > + eqnx_dscope[d].q.q_end) + eqnx_dscope[d].next = + eqnx_dscope[d].q.q_begin; + ubase += move_cnt; + room -= move_cnt; + } + eqnx_dscope[d].scope_wait_wait = 0; + wake_up_interruptible(&eqnx_dscope[d]. + scope_wait); + } + mpc->xmit_tail += c; + mpc->xmit_tail &= XMIT_BUF_SIZE - 1; + mpc->xmit_cnt -= c; + } + oldreg = icpo->cout_cpu_req; + icpo->cout_cpu_req |= TX_SUSP; + eqnx_chnl_sync(mpc); + qcnt = SSTRD16(icpq->q_data_count); + if (qcnt < 9) { + if ((icpo->cout_intnl_svd_toggls & 0x4) == + (icpo->cout_cpu_req & 0x4)) { + icpo->cout_cpu_req ^= (CPU_SND_REQ); + mpc->mpc_cout_events &= ~EV_CPU_REQ_DN; + } + } + qcnt += (c + nx); + SSTWR16(icpq->q_data_count, qcnt); + space -= (c + nx); + dev_dbg(mpd->dev, "eqnx_write_SSP4: wrote %d chars for " + "device %d\n", (c + nx), + SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + if (!(oldreg & TX_SUSP)) + icpo->cout_cpu_req &= ~TX_SUSP; + mpc->mpc_output += (c + nx); + attn_enbl = SSTRD16(icpo->cout_attn_enbl); + attn_enbl |= (ENA_TX_EMPTY_Q0 | ENA_TX_LOW_Q0); + SSTWR16(icpo->cout_attn_enbl, attn_enbl); + } + if ((mpc->xmit_cnt < block_count) && (func_type == EQNX_TXINT)) { + dev_dbg(mpd->dev, "eqnx_write_SSP4: calling write_wakeup " + "for device = %d\n", + SSTMINOR(mpc->mpc_major, mpc->mpc_minor)); + /* signal write wakeup when safe to call ldisc */ + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + eqnx_write_wakeup_deferred++; + wake_up_interruptible(&tty->write_wait); + } +} + +/* + * delay_jiffies(len) + * + * Delay by the specified number of jiffies. + * + * len = jiffies to delay. + */ +static void delay_jiffies(int len) +{ + if (len > 0) { + msleep(jiffies_to_msecs(len)); + } +} - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |