FreeBSD-5.3/sys/dev/sx/sx_util.c

Compare this file to the similar file:
Show the results in this format:

/*
 * Device driver for Specialix I/O8+ multiport serial card.
 *
 * Copyright 2003 Frank Mayhar <frank@exit.com>
 *
 * Derived from the "si" driver by Peter Wemm <peter@netplex.com.au>, using
 * lots of information from the Linux "specialix" driver by Roger Wolff
 * <R.E.Wolff@BitWizard.nl> and from the Intel CD1865 "Intelligent Eight-
 * Channel Communications Controller" datasheet.  Roger was also nice
 * enough to answer numerous questions about stuff specific to the I/O8+
 * not covered by the CD1865 datasheet.
 *
 * 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
 *    notices, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notices, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY ``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 AUTHORS BE LIABLE.
 *
 * $FreeBSD: src/sys/dev/sx/sx_util.c,v 1.1 2004/04/11 19:32:20 imp Exp $
 */

#include "opt_debug_sx.h"

/* Utility and support routines for the Specialix I/O8+ driver. */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/tty.h>
#include <machine/resource.h>   
#include <machine/bus.h>
#include <machine/clock.h>
#include <sys/rman.h>

#include <dev/sx/cd1865.h>
#include <dev/sx/sxvar.h>
#include <dev/sx/sx.h>
#include <dev/sx/sx_util.h>

/*
 * sx_probe_io8()
 *	Probe the board to verify that it is a Specialix I/O8+.
 *
 * Description:
 *	This is called by sx_pci_attach() (and possibly in the future by
 *	sx_isa_attach()) to verify that the card we're attaching to is
 *	indeed a Specialix I/O8+.  To do this, we check for the Prescaler
 *	Period Register of the CD1865 chip and for the Specialix signature
 *	on the DSR input line of each channel.  These lines, along with the
 *	RTS output lines, are wired down in hardware.
 */
int
sx_probe_io8(
	device_t dev)
{
	struct sx_softc *sc;
	unsigned char val1, val2;
	int i;

	sc = device_get_softc(dev);
	/*
	 * Try to write the Prescaler Period Register, then read it back,
	 * twice.  If this fails, it's not an I/O8+.
	 */
	sx_cd1865_out(sc, CD1865_PPRL, 0x5a);
	DELAY(1);
	val1 = sx_cd1865_in(sc, CD1865_PPRL);

	sx_cd1865_out(sc, CD1865_PPRL, 0xa5);
	DELAY(1);
	val2 = sx_cd1865_in(sc, CD1865_PPRL);

	if ((val1 != 0x5a) || (val2 != 0xa5))
		return(1);

	/*
	 * Check the lines that Specialix uses as board identification.
	 * These are the DSR input and the RTS output, which are wired
	 * down.
	 */
	val1 = 0;
	for (i = 0; i < 8; i++) {
		sx_cd1865_out(sc, CD1865_CAR, i);	/* Select channel.    */
		if (sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_DSR) /* Set?  */
			val1 |= 1 << i;			/* OR it in.          */
	}
#ifdef notdef
	val2 = 0;
	for (i = 0; i < 8; i++) {
		sx_cd1865_out(sc, CD1865_CAR, i);	/* Select channel.    */
		if (sx_cd1865_in(sc, CD1865_MSVR) & CD1865_MSVR_RTS) /* Set?  */
			val2 |= 1 << i;			/* OR it in.          */
	}
	/*
	 * They managed to switch the bit order between the docs and
	 * the IO8+ card. The new PCI card now conforms to old docs.
	 * They changed the PCI docs to reflect the situation on the
	 * old card.
	 */
	val2 = (bp->flags & SX_BOARD_IS_PCI) ? 0x4d : 0xb2;
#endif /* notdef */
	if (val1 != 0x4d) {
		if (bootverbose)
			device_printf(dev,
				      "Specialix I/O8+ ID 0x4d not found (0x%02x).\n",
				      val1);
		return(1);
	}
	return(0);		/* Probed successfully.                       */
}

/*
 * sx_init_CD1865()
 *	Hard-reset and initialize the I/O8+ CD1865 processor.
 *
 * Description:
 *	This routine does a hard reset of the CD1865 chip and waits for it
 *	to complete.  (The reset should complete after 500us; we wait 1ms
 *	and fail if we time out.)  We then initialize the CD1865 processor.
 */
int
sx_init_cd1865(
	struct sx_softc *sc,
	int unit)
{
	int s;
	unsigned int to;

	s = spltty();
	disable_intr();
	sx_cd1865_out(sc, CD1865_GSVR, 0x00); /* Clear the GSVR.              */
	sx_cd1865_wait_CCR(sc, 0);	/* Wait for the CCR to clear.         */
	sx_cd1865_out(sc, CD1865_CCR, CD1865_CCR_HARDRESET); /* Reset CD1865. */
	enable_intr();
	to = SX_GSVR_TIMEOUT/5;
	while (to-- > 0) {
		if (sx_cd1865_in(sc, CD1865_GSVR) == 0xff)
			break;
		DELAY(5);
	}
	if (to == 0) {
		splx(s);
		printf("sx%d:  Timeout waiting for reset.\n", unit);
		return(EIO);
	}
	/*
	 * The high five bits of the Global Interrupt Vector Register is
	 * used to identify daisy-chained CD1865 chips.  The I/O8+ isn't
	 * daisy chained, but we have to initialize the field anyway.
	 */
	sx_cd1865_out(sc, CD1865_GIVR, SX_CD1865_ID);
	/* Clear the Global Interrupting Channel register. */
	sx_cd1865_out(sc, CD1865_GICR, 0);
	/*
	 * Set the Service Match Registers to the appropriate values.  See
	 * the cd1865.h include file for more information.
	 */
	sx_cd1865_out(sc, CD1865_MSMR, CD1865_ACK_MINT); /* Modem.            */
	sx_cd1865_out(sc, CD1865_TSMR, CD1865_ACK_TINT); /* Transmit.         */
	sx_cd1865_out(sc, CD1865_RSMR, CD1865_ACK_RINT); /* Receive.          */
	/*
	 * Set RegAckEn in the Service Request Configuration Register;
	 * we'll be acknowledging service requests in software, not
	 * hardware.
	 */
	sx_cd1865_bis(sc, CD1865_SRCR, CD1865_SRCR_REGACKEN);
	/*
	 * Set the CD1865 timer tick rate.  The value here is the processor
	 * clock rate (in MHz) divided by the rate in ticks per second.  See
	 * commentary in sx.h.
	 */
	sx_cd1865_out(sc, CD1865_PPRH, SX_CD1865_PRESCALE >> 8);
	sx_cd1865_out(sc, CD1865_PPRL, SX_CD1865_PRESCALE & 0xff);

	splx(s);
	return(0);
}

#ifdef notyet
/*
 * Set the IRQ using the RTS lines that run to the PAL on the board....
 *
 * This is a placeholder for ISA support, if that's ever implemented.  This
 * should _only_ be called from sx_isa_attach().
 */
int
sx_set_irq(
	struct sx_softc *sc,
	int unit,
	int irq)
{
	register int virq;
	register int i, j;

	switch (irq) {
	/* In the same order as in the docs... */
		case 15:
			virq = 0;
			break;
		case 12:
			virq = 1;
			break;
		case 11:
			virq = 2;
			break;
		case 9:
			virq = 3;
			break;
		default:
			printf("sx%d:  Illegal irq %d.\n", unit, irq);
			return(0);
	}
	for (i = 0; i < 2; i++) {
		sx_cd1865_out(sc, CD1865_CAR, i); /* Select channel.          */
		j =  ((virq >> i) & 0x1) ? MSVR_RTS : 0;
		sx_cd1865_out(sc, CD1865_MSVRTS, j);
	}
	return(1);
}

#endif /* notyet */

/*
 * sx_int_port()
 *	Determine the port that interrupted us.
 *
 * Description:
 *	This routine checks the Global Interrupting Channel Register (GICR)
 *	to find the port that caused an interrupt.  It returns a pointer to
 *	the sx_port structure of the interrupting port, or NULL if there was
 *	none.
 *
 * XXX - check type/validity of interrupt?
 */
struct sx_port *
sx_int_port(
	struct sx_softc *sc,
	int unit)
{
	unsigned char chan;
	struct sx_port *pp;
	
	chan = (sx_cd1865_in(sc, CD1865_GSCR2|SX_EI) & CD1865_GICR_CHAN_MASK)
						      >> CD1865_GICR_CHAN_SHIFT;
	DPRINT((NULL, DBG_INTR, "Intr chan %d\n", chan));
	if (chan < CD1865_NUMCHAN) {
		pp = sc->sc_ports + (int)chan;
		return(pp);
	}
	printf("sx%d: False interrupt on port %d.\n", unit, chan);
	return(NULL);
}