/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Serial I/O driver for Z8530 chips
*/
#include <sys/sysmacros.h>
#include <sys/machsystm.h>
#define ZS_TRACING
#ifdef ZS_TRACING
/*
* Temp tracepoint definitions
*/
#endif /* ZSH_TRACING */
#ifndef MAXZS
#endif
int nzs = 0;
char *zssoftCAR;
#ifdef ZS_DEBUG
int zs_h_log_n = 0;
#define zs_h_log_add(c) \
{ \
if (zs_h_log_n >= ZS_H_LOG_MAX) \
zs_h_log_n = 0; \
zs_h_log[zs_h_log_n++] = c; \
}
#else /* NO_ZS_DEBUG */
#define zs_h_log_add(c)
#endif /* ZS_DEBUG */
/*
* Driver data
*/
int zssoftpend;
static int zs_addedsoft = 0;
/*
* Driver information for auto-configuration stuff.
*/
};
void **result);
/* common to sev. streams or ports */
extern kcondvar_t lbolt_cv;
/*
* curently the only spin lock level 12 for all ocasions
*/
nulldev, /* cb_open */
nulldev, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
nodev, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
&asynctab, /* cb_stream */
(int)(ZSS_CONF_FLAG) /* cb_flag */
};
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
zsc_info, /* devo_getinfo */
nulldev, /* devo_identify */
zsprobe, /* devo_probe */
zsattach, /* devo_attach */
zsdetach, /* devo_detach */
nodev, /* devo_reset */
&cb_zs_ops, /* devo_cb_ops */
ddi_power, /* devo_power */
ddi_quiesce_not_supported, /* devo_quiesce */
};
/*
* This is the loadable module wrapper.
*/
/*
* Module linkage information for the kernel.
*/
extern struct mod_ops mod_driverops;
&mod_driverops, /* Type of module. This one is a driver */
"Z8530 serial driver",
&zs_ops, /* driver ops */
};
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (EBUSY);
}
int
{
}
static int
{
int rval;
auto char c;
/*
* temporarily map in registers
*/
return (rval);
}
/*
* NON-DDI Compliant call
*/
/*
* get in sync with the chip
*/
goto out;
}
drv_usecwait(2);
/*
* The traditional test for the presence of an 8530 has been to write
* a 15 (octal 017) to its control register address, then read it back.
* A Z8530 will respond to this with the contents of Read-Register 15.
* If this address were memory, or something else, we would expect to
* see the 15 again. Normally, the contents of RR15 will be entirely
* different. A Z8530 does not use the D0 and D2 bits of register 15,
* so they should equal zero. Compatable chips should do the same.
* Beware of "enhanced" SCC's that do not guarantee this.
*/
!= DDI_SUCCESS) {
goto out;
}
drv_usecwait(2);
goto out;
}
drv_usecwait(2);
if (c & 5) {
goto out;
}
out:
/*
* NON-DDI Compliant call
*/
return (rval);
}
/*ARGSUSED*/
static int
{
int loops, i;
uint_t s;
int rtsdtr_bits = 0;
char softcd;
int keyboard_prop;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/*
* Try to resume first channel
*/
return (DDI_FAILURE);
/*
* And the second channel
*/
zs++;
zs--;
if (!zs->zs_suspend ||
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
KM_SLEEP);
/* don't set nzs until arrays are allocated */
zs_usec_delay = (i <= 0) ? 1 : i;
}
"zs: unable to allocate resources for chip %d.",
return (DDI_FAILURE);
}
/*
* map in the device registers
*/
return (DDI_FAILURE);
}
/*
* Non-DDI compliant Sun-Ness specfic call(s)
*/
/*
* Stop the monitor's polling interrupt.
*
* I know that this is not exactly obvious. On all sunmon PROM
* machines, the PROM has can have a high level periodic clock
* interrupt going at this time. It uses this periodic interrupt
* to poll the console tty or kbd uart to check for things like
* BREAK or L1-A (abort). While we're probing this device out we
* have to shut that off so the PROM won't get confused by what
* we're doing to the zs. This has caused some pretty funny bugs
* in its time.
*
* For OPENPROM machines, the prom takes level12 interrupts directly,
* but we call this routine anyway (I forget why).
*/
/*
* Go critical to keep uart from urking.
*/
s = ddi_enter_critical();
/*
* We are about to issue a full reset to this chip.
* First, now that interrupts are blocked, we will delay up to a
* half-second, checking both channels for any stray activity.
* Next we will preserve the time constants from both channels,
* so that they can be restored after the reset. This is especially
* important for the console device. Finally, do the reset and
* follow it with an extended recovery while the chip settles down.
*/
if ((rr & ZSRR1_ALL_SENT) == 0) continue;
if ((rr & ZSRR1_ALL_SENT) == 0) continue;
if ((rr & ZSRR0_TX_READY) == 0) continue;
if ((rr & ZSRR0_TX_READY) != 0) break;
}
DELAY(10);
/*
* Set up the other components of the zscom structs for this chip.
*/
for (i = 0; i < 2; i++) {
/*
* Property for ignoring DCD.
* We switch between 'a' and 'b' ports for this device.
*/
/*
* For this channel, set the hardware address, allocate the
* high-level mutex, and update the zscurr pointer.
* The high-level lock is shared by both channels because
* 8530 register addressing is non-atomic and asymetrical.
* Multiple threads crossing paths during this operation
* could trash the chip, and thus, possibly the system console.
*/
if (i == 0) { /* port A */
} else { /* port B */
zs++;
}
if (softcd)
"keyboard", 0);
/*
* Set up the default asynch modes
* so the monitor will still work
*/
/*
* The SYNC pin on the second SCC (keyboard & mouse) may not
* be connected and noise creates transitions on this line.
* This floods the system with interrupts, unless the
* register 15 with everything we need but that one.
*/
if (keyboard_prop) {
}
}
DELAY(4000);
/*
* Two levels of interrupt - chip interrupts at a high level (12),
* (which is seen below as zs_high_intr), and then as a secondary
* stage soft interrupt as seen in zsintr below.
*
* Because zs_high_intr does a window save, as well as calls to
* other functions, we cannot install it as a "fast" interrupt
* that would execute right out of the trap window. Too bad...
*/
(ddi_idevice_cookie_t *)0, zs_high_intr,
(caddr_t)0) != DDI_SUCCESS) {
/*NOTREACHED*/
}
if (zs_addedsoft == 0) {
&zs_iblock, (ddi_idevice_cookie_t *)0,
"cannot set second stage zs interrupt");
/*NOTREACHED*/
}
zs_addedsoft++; /* we only need one zsintr! */
}
(void) ddi_exit_critical(s);
/*
* Non-DDI compliant Sun-Ness specific call
*/
mon_clock_start(); /* re-enable monitor's polling interrupt */
/*
* Export names for channel a or b consconfig match...
* The names exported to the filesystem include the
* designated tty'a' type name and may not match the PROM
* pathname.
* Note the special name "obp-console-name" used in these calls.
* This keeps the ports and devlinks programs from seeing these
* names. (But allows ddi_pathname_to_dev_t to see them.)
* We don't need to do this if the instance number is zero,
* because we'll create them below, in this case.
*/
if (ddi_get_instance(dev) != 0) {
/*
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
/*
* Export normal device names...
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
} else {
/*
* Create keyboard and mouse nodes which devfs doesn't see
*/
/*
* This set of minor nodes is for use with the consconfig_dacf
* module for the sun4u platforms. (See PSARC/1998/212)
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* These minor nodes are for use with pre-sun4u platforms.
* Either one set or the other will be opened by consconfig.
*/
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
/*
* Initialize power management bookkeeping; components are
* created idle.
*/
(void) pm_busy_component(dev, 0);
} else {
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static int
{
switch (cmd) {
case DDI_DETACH:
return (DDI_FAILURE);
case DDI_SUSPEND:
/*
* Try to suspend first channel
*/
return (DDI_FAILURE);
/*
* And the second channel
*/
zs++;
zs--;
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
/*
* SCC High Level Interrupt Handler
*
* This routine fields the level 12 interrupts generated by the 8530 chips.
* When the SCC interrupts the conditions that triggered it are available
* for reference in Read Register 3 of the A channel (RR3A). We process
* all the pending interrupts before returning. The maximum interrupts
* that will be processed before returning is set to 6, which is twice
* the size of RX-FIFO.
* We keep a pointer to the B side of the most recently interrupting chip
* in zscurr.
*/
/*
* 'argzs' actually 'struct zscom *argzs'
*/
/*ARGSUSED*/
{
int unit;
if (isource & ZSRR3_IP_B_STAT)
else {
if (isource & ZSRR3_IP_B_TX)
if (isource & ZSRR3_IP_B_RX) {
if (stat & ZSRR1_ANY_ERRORS)
else if ((SCC_READ0()) & ZSRR0_RX_READY)
}
}
zs -= 1;
if (isource & ZSRR3_IP_A_STAT)
else {
if (isource & ZSRR3_IP_A_TX)
if (isource & ZSRR3_IP_A_RX) {
if (stat & ZSRR1_ANY_ERRORS)
else if ((SCC_READ0()) & ZSRR0_RX_READY)
}
}
}
if (count == ZS_HIGH_INTR_LOOPLIMIT) {
while (unit--) {
continue;
if (isource & ZSRR3_INT_PENDING) {
goto start_zs_h;
}
}
}
return (DDI_INTR_UNCLAIMED); /* Must not be for us. */
}
}
return (DDI_INTR_CLAIMED);
}
/*
* Handle a second-stage interrupt.
*/
/*ARGSUSED*/
{
int rv;
/*
* Test and clear soft interrupt.
*/
"zs_int start");
rv = zssoftpend;
if (rv != 0) {
zssoftpend = 0;
}
if (rv) {
}
}
}
}
"zs_int end");
return (rv);
}
void
setzssoft(void)
{
}
/*
* Install a new ops vector into low level vector routine addresses
*/
void
{
}
/*
* Set or get the modem control status.
*
* This routine relies on the fact that the bits of interest in RR0 (CD and
* CTS) do not overlap the bits of interest in WR5 (RTS and DTR). Thus, they
* can be combined into a single 'int' without harm.
*/
int
{
ZSDELAY();
switch (how) {
case DMSET:
break;
case DMBIS:
break;
case DMBIC:
break;
case DMGET:
return (mbits);
}
now = gethrestime_sec();
/*
* if DTR is going low, stash current time away
*/
/*
* if DTR is going high, sleep until it has been low a bit
*/
if (zs->zs_suspended)
goto again;
}
return (mbits);
}
/*
* Program the Z8530 registers.
*/
void
{
int loops;
uchar_t c;
/*
* There are some special cases to account for before reprogramming.
* We might be transmitting, so delay 100,000 usec (worst case at 110
* baud) for this to finish, then disable the receiver until later,
* reset the External Status Change latches and the error bits, and
* drain the receive FIFO.
* XXX: Doing any kind of reset (WR9) here causes trouble!
*/
wr10 |= ZSWR10_NRZI;
} else {
SCC_READ(1, c);
if (c & ZSRR1_ALL_SENT)
break;
DELAY(100);
}
SCC_WRITE(3, 0);
c = SCC_READDATA(); /* Empty the FIFO */
c = SCC_READDATA();
c = SCC_READDATA();
}
/*
* Programming the SCC is done in three phases.
* Phase one sets operating modes:
*/
} else
/*
* Phase two enables special hardware functions:
*/
wr14 |= ZSWR14_AUTO_ECHO;
} else {
SCC_WRITE(1, 0);
}
/*
* Phase three enables interrupt sources:
*/
}
static void
{
short c;
c = SCC_READDATA();
ZSDELAY();
#ifdef lint
c = c;
#endif /* lint */
}
static int
{
return (0);
}
/*
*/
static int
{
/*
* Get a copy of the current registers
*/
return (DDI_SUCCESS);
}
static int
{
/*
* Restore registers
*/
DELAY(4000);
return (DDI_SUCCESS);
}