/*	$OpenBSD: com_pcmcia.c,v 1.3 1998/03/05 14:39:38 niklas Exp $	*/
/*	$NetBSD: com.c,v 1.82.4.1 1996/06/02 09:08:00 mrg Exp $	*/

/*-
 * Copyright (c) 1993, 1994, 1995, 1996
 *	Charles M. Hannum.  All rights reserved.
 * Copyright (c) 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/tty.h>

#include <machine/bus.h>
#include <machine/intr.h>

#include <dev/isa/isavar.h>

#include <dev/ic/comreg.h>
#include <dev/ic/comvar.h>
#include <dev/ic/ns16550reg.h>

#include <dev/pcmcia/pcmciavar.h>

/* Macros to clear/set/test flags. */
#define	SET(t, f)	(t) |= (f)
#define	CLR(t, f)	(t) &= ~(f)
#define	ISSET(t, f)	((t) & (f))

int	com_pcmcia_match __P((struct device *, void *, void *));
void	com_pcmcia_attach __P((struct device *, struct device *, void *));
int	com_pcmcia_detach __P((struct device *));

int	com_pcmcia_mod __P((struct pcmcia_link *pc_link, struct device *self,
	    struct pcmcia_conf *pc_cf, struct cfdata *cf));
int	com_pcmcia_isa_attach __P((struct device *, void *, void *,
	    struct pcmcia_link *));
int	com_pcmcia_remove __P((struct pcmcia_link *, struct device *));
int	com_pcmcia_probe __P((struct device *, void *, void *));

struct cfattach com_pcmcia_ca = {
	sizeof(struct com_softc), com_pcmcia_match, com_pcmcia_attach,
	com_pcmcia_detach
};

/* additional setup needed for pcmcia devices */
/* modify config entry */
int 
com_pcmcia_mod(pc_link, self, pc_cf, cf)
    struct pcmcia_link *pc_link;
    struct device *self;
    struct pcmcia_conf *pc_cf; 
    struct cfdata *cf;
{               
	int err; 

	if (!(err = PCMCIA_BUS_CONFIG(pc_link->adapter, pc_link, self,
	    pc_cf, cf))) {
		pc_cf->memwin = 0;
		if (pc_cf->cfgtype == 0) 
		pc_cf->cfgtype = CFGENTRYID; /* determine from ioaddr */
	}
	return err;
}

static struct pcmcia_com {
	struct pcmcia_device pcd;
} pcmcia_com =  {
	{"PCMCIA Modem card", com_pcmcia_mod, com_pcmcia_isa_attach,
	 NULL, com_pcmcia_remove}
};          


struct pcmciadevs pcmcia_com_devs[] = {
	{ "com", 0,
	NULL, "*MODEM*", NULL, NULL,
	NULL, (void *)&pcmcia_com 
	},
	{ "com", 0,
	NULL, NULL, "*MODEM*", NULL,
	NULL, (void *)&pcmcia_com 
	},
	{ "com", 0,
	NULL, NULL, NULL, "*MODEM*",
	NULL, (void *)&pcmcia_com 
	},
	{NULL}
};
#define ncom_pcmcia_devs sizeof(pcmcia_com_devs)/sizeof(pcmcia_com_devs[0])

int
com_pcmcia_match(parent, match, aux)
	struct device *parent;
	void *match, *aux;
{
	return pcmcia_slave_match(parent, match, aux, pcmcia_com_devs,
	    ncom_pcmcia_devs);
}

int
com_pcmcia_isa_attach(parent, match, aux, pc_link)
	struct device *parent;
	void *match;
	void *aux;
	struct pcmcia_link *pc_link;
{
	struct isa_attach_args *ia = aux;
	struct com_softc *sc = match;
	int rval;

	if ((rval = com_pcmcia_probe(parent, sc->sc_dev.dv_cfdata, ia))) {
		if (ISSET(pc_link->flags, PCMCIA_REATTACH)) {
#ifdef COM_DEBUG
			printf("comreattach, hwflags=%x\n", sc->sc_hwflags);
#endif
			sc->sc_hwflags = COM_HW_REATTACH | (sc->sc_hwflags &
			    (COM_HW_ABSENT_PENDING|COM_HW_CONSOLE));
		} else
			sc->sc_hwflags = 0;
		sc->sc_ic = ia->ia_ic;
	}
	return rval;
}


/*
 * Called by config_detach attempts, shortly after com_pcmcia_remove
 * was called.
 */
int
com_pcmcia_detach(self)
	struct device *self;
{
	struct com_softc *sc = (void *)self;

	if (ISSET(sc->sc_hwflags, COM_HW_ABSENT_PENDING)) {
		/* don't let it really be detached, it is still open */
		return EBUSY;
	}
	return 0;		/* OK! */
}

/*
 * called by pcmcia framework to accept/reject remove attempts.
 * If we return 0, then the detach will proceed.
 */
int
com_pcmcia_remove(pc_link, self)
	struct pcmcia_link *pc_link;
	struct device *self;
{
	struct com_softc *sc = (void *)self;
	struct tty *tp;
	int s;

	if (!sc->sc_tty)
		goto ok;
	tp = sc->sc_tty;

	/* not in use ?  if so, return "OK" */
	if (!ISSET(tp->t_state, TS_ISOPEN) &&
	    !ISSET(tp->t_state, TS_WOPEN)) {
		ttyfree(sc->sc_tty);
		sc->sc_tty = NULL;
    ok:
		isa_intr_disestablish(sc->sc_ic, sc->sc_ih);
		sc->sc_ih = NULL;
		SET(sc->sc_hwflags, COM_HW_ABSENT);
		return 0;		/* OK! */
	}
	/*
	 * Not easily removed.  Put device into a dead state, clean state
	 * as best we can.  notify all waiters.
	 */
	SET(sc->sc_hwflags, COM_HW_ABSENT|COM_HW_ABSENT_PENDING);
#ifdef COM_DEBUG
	printf("pending detach flags %x\n", sc->sc_hwflags);
#endif

	s = spltty();
	com_absent_notify(sc);
	splx(s);

	return 0;
}

#if 0
void
com_pcmcia_attach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct pcmcia_attach_args *paa = aux;
	
	printf("com_pcmcia_attach %p %p %p\n", parent, self, aux);
	delay(2000000);
	if (!pcmcia_configure(parent, self, paa->paa_link)) {
		struct com_softc *sc = (void *)self;
		sc->sc_hwflags |= COM_HW_ABSENT;
		printf(": not attached\n");
	}
}
#endif

int
com_pcmcia_probe(parent, match, aux)
	struct device *parent;
	void *match, *aux;
{
	bus_space_tag_t iot;
	bus_space_handle_t ioh;
	int iobase, needioh;
	int rv = 1;
	struct isa_attach_args *ia = aux;

	iot = ia->ia_iot;
	iobase = ia->ia_iobase;
	needioh = 1;

	/* if it's in use as console, it's there. */
	if (iobase == comconsaddr && !comconsattached)
		goto out;

	if (needioh && bus_space_map(iot, iobase, COM_NPORTS, 0, &ioh)) {
		rv = 0;
		goto out;
	}
	rv = comprobe1(iot, ioh);
	if (needioh)
		bus_space_unmap(iot, ioh, COM_NPORTS);

out:
	ia->ia_iosize = COM_NPORTS;
	ia->ia_msize = 0;
	return (rv);
}

void
com_pcmcia_attach(parent, self, aux)
	struct device *parent, *self;
	void *aux;
{
	struct com_softc *sc = (void *)self;
	int iobase, irq;
	bus_space_tag_t iot;
	bus_space_handle_t ioh;
	struct isa_attach_args *ia = aux;

	if (ISSET(sc->sc_hwflags, COM_HW_REATTACH)) {
		int s;
		s = spltty();
		com_absent_notify(sc);
		splx(s);
	} else
	    sc->sc_hwflags = 0;
	sc->sc_swflags = 0;

	/*
	 * We're living on an isa.
	 */
	iobase = ia->ia_iobase;
	iot = ia->ia_iot;
        if (iobase != comconsaddr) {
                if (bus_space_map(iot, iobase, COM_NPORTS, 0, &ioh))
			panic("comattach: io mapping failed");
	} else
                ioh = comconsioh;
	irq = ia->ia_irq;

	sc->sc_iot = iot;
	sc->sc_ioh = ioh;
	sc->sc_iobase = iobase;

	if (iobase == comconsaddr) {
		comconsattached = 1;

		/* 
		 * Need to reset baud rate, etc. of next print so reset
		 * comconsinit.  Also make sure console is always "hardwired".
		 */
		delay(1000);			/* wait for output to finish */
		comconsinit = 0;
		SET(sc->sc_hwflags, COM_HW_CONSOLE);
		SET(sc->sc_swflags, COM_SW_SOFTCAR);
	}

	/* look for a NS 16550AF UART with FIFOs */
	bus_space_write_1(iot, ioh, com_fifo,
	    FIFO_ENABLE | FIFO_RCV_RST | FIFO_XMT_RST | FIFO_TRIGGER_14);
	delay(100);
	if (ISSET(bus_space_read_1(iot, ioh, com_iir), IIR_FIFO_MASK) ==
	    IIR_FIFO_MASK) {
		if (ISSET(bus_space_read_1(iot, ioh, com_fifo),
		    FIFO_TRIGGER_14) == FIFO_TRIGGER_14) {
			SET(sc->sc_hwflags, COM_HW_FIFO);
			printf(": ns16550a, working fifo\n");
		} else
			printf(": ns16550, broken fifo\n");
	} else
		printf(": ns8250 or ns16450, no fifo\n");
	bus_space_write_1(iot, ioh, com_fifo, 0);

	/* disable interrupts */
	bus_space_write_1(iot, ioh, com_ier, 0);
	bus_space_write_1(iot, ioh, com_mcr, 0);

	if (irq != IRQUNK) {
		struct isa_attach_args *ia = aux;

		sc->sc_ih = isa_intr_establish(ia->ia_ic, irq,
		    IST_EDGE, IPL_TTY, comintr, sc, sc->sc_dev.dv_xname);
	}

#ifdef KGDB
	if (kgdb_dev == makedev(commajor, unit)) {
		if (ISSET(sc->sc_hwflags, COM_HW_CONSOLE))
			kgdb_dev = -1;	/* can't debug over console port */
		else {
			cominit(iot, ioh, kgdb_rate);
			if (kgdb_debug_init) {
				/*
				 * Print prefix of device name,
				 * let kgdb_connect print the rest.
				 */
				printf("%s: ", sc->sc_dev.dv_xname);
				kgdb_connect(1);
			} else
				printf("%s: kgdb enabled\n",
				    sc->sc_dev.dv_xname);
		}
	}
#endif

	/* XXX maybe move up some? */
	if (ISSET(sc->sc_hwflags, COM_HW_CONSOLE))
		printf("%s: console\n", sc->sc_dev.dv_xname);
}