/* $Id: tpam_main.c,v 1.1.2.2 2001/07/11 12:22:59 kai Exp $
 *
 * Turbo PAM ISDN driver for Linux. (Kernel Driver - main routines)
 *
 * Copyright 2001 Stelian Pop <stelian.pop@fr.alcove.com>, Alcôve
 *
 * For all support questions please contact: <support@auvertech.fr>
 *
 * 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, 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.
 *
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/tqueue.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <asm/io.h>

#include "tpam.h"

/* Local functions prototypes */
static int __devinit tpam_probe(struct pci_dev *, const struct pci_device_id *);
static void __devexit tpam_unregister_card(tpam_card *);
static void __devexit tpam_remove(struct pci_dev *);
static int __init tpam_init(void);
static void __exit tpam_exit(void);

/* List of boards */
static tpam_card *cards; /* = NULL; */
/* Number of cards */
static int cards_num;
/* Configurable id of the driver */
static char *id = "tpam\0\0\0\0\0\0\0\0\0\0\0\0";

MODULE_DESCRIPTION("Driver for TurboPAM cards");
MODULE_AUTHOR("Stelian Pop, Alcove");
MODULE_SUPPORTED_DEVICE("ISDN subsystem");
MODULE_PARM_DESC(id,"ID-String of the driver");
MODULE_PARM(id,"s");

/*
 * Finds a board by its driver ID.
 *
 * 	driverId: driver ID (as referenced by the IDSN link layer)
 *
 * Return: the tpam_card structure if found, NULL on error.
 */
tpam_card *tpam_findcard(int driverid) {
	tpam_card *p = cards;

	while (p) {
		if (p->id == driverid)
			return p;
		p = p->next;
	}
	return NULL;
}

/*
 * Finds a channel number by its ncoid.
 *
 * 	card: the board
 * 	ncoid: the NCO id
 *
 * Return: the channel number if found, TPAM_CHANNEL_INVALID if not.
 */
u32 tpam_findchannel(tpam_card *card, u32 ncoid) {
	int i;

	for (i = 0; i < TPAM_NBCHANNEL; ++i)
		if (card->channels[i].ncoid == ncoid)
			return card->channels[i].num;
	return TPAM_CHANNEL_INVALID;
}

/*
 * Initializes and registers a new TurboPAM card.
 *
 * 	dev: the PCI device
 * 	num: the board number
 *
 * Return: 0 if OK, <0 if error
 */
static int __devinit tpam_probe(struct pci_dev *dev, const struct pci_device_id *pci_id) {
	tpam_card *card, *c;
	int i;

	/* allocate memory for the board structure */
	if (!(card = (tpam_card *)kmalloc(sizeof(tpam_card), GFP_KERNEL))) {
		printk(KERN_ERR "TurboPAM: tpam_register_card: "
		       "kmalloc failed!\n");
		return -ENOMEM;
	}

	memset((char *)card, 0, sizeof(tpam_card));

	card->irq = dev->irq;
	card->lock = SPIN_LOCK_UNLOCKED;
	sprintf(card->interface.id, "%s%d", id, cards_num);

	/* request interrupt */
	if (request_irq(card->irq, &tpam_irq, SA_INTERRUPT | SA_SHIRQ, 
			card->interface.id, card)) {
		printk(KERN_ERR "TurboPAM: tpam_register_card: "
		       "could not request irq %d\n", card->irq);
		kfree(card);
		return -EIO;
	}

	/* remap board memory */
	if (!(card->bar0 = (unsigned long) ioremap(pci_resource_start(dev, 0),
						   0x800000))) {
		printk(KERN_ERR "TurboPAM: tpam_register_card: "
		       "unable to remap bar0\n");
		free_irq(card->irq, card);
		kfree(card);
		return -EIO;
	}

	/* reset the board */
	readl(card->bar0 + TPAM_RESETPAM_REGISTER);

	/* initialisation magic :-( */
	copy_to_pam_dword(card, (void *)0x01800008, 0x00000030);
	copy_to_pam_dword(card, (void *)0x01800010, 0x00000030);
	copy_to_pam_dword(card, (void *)0x01800014, 0x42240822);
	copy_to_pam_dword(card, (void *)0x01800018, 0x07114000);
	copy_to_pam_dword(card, (void *)0x0180001c, 0x00000400);
	copy_to_pam_dword(card, (void *)0x01840070, 0x00000010);

	/* fill the ISDN link layer structure */
	card->interface.channels = TPAM_NBCHANNEL;
	card->interface.maxbufsize = TPAM_MAXBUFSIZE;
	card->interface.features = 
		ISDN_FEATURE_P_EURO |
		ISDN_FEATURE_L2_HDLC |
		ISDN_FEATURE_L2_MODEM |
		ISDN_FEATURE_L3_TRANS;
	card->interface.hl_hdrlen = 0;
	card->interface.command = tpam_command;
	card->interface.writebuf_skb = tpam_writebuf_skb;
	card->interface.writecmd = NULL;
	card->interface.readstat = NULL;

	/* register wrt the ISDN link layer */
	if (!register_isdn(&card->interface)) {
		printk(KERN_ERR "TurboPAM: tpam_register_card: "
		       "unable to register %s\n", card->interface.id);
		free_irq(card->irq, card);
		iounmap((void *)card->bar0);
		kfree(card);
		return -EIO;
	}
	card->id = card->interface.channels;

	/* initialize all channels */
	for (i = 0; i < TPAM_NBCHANNEL; ++i) {
		card->channels[i].num = i;
		card->channels[i].card = card;
		card->channels[i].ncoid = TPAM_NCOID_INVALID;
		card->channels[i].hdlc = 0;
		card->channels[i].realhdlc = 0;
		card->channels[i].hdlcshift = 0;
		skb_queue_head_init(&card->channels[i].sendq);
	}

	/* initialize the rest of board structure */
	card->channels_used = 0;
	card->channels_tested = 0;
	card->running = 0;
	card->busy = 0;
	card->roundrobin = 0;
	card->loopmode = 0;
	skb_queue_head_init(&card->sendq);
	skb_queue_head_init(&card->recvq);
	card->recv_tq.routine = (void *) (void *) tpam_recv_tq;
	card->recv_tq.data = card;
	card->send_tq.routine = (void *) (void *) tpam_send_tq;
	card->send_tq.data = card;

	/* add the board at the end of the list of boards */
	card->next = NULL;
	if (cards) {
		c = cards;
		while (c->next)
			c = c->next;
		c->next = card;
	}
	else
		cards = card;

	++cards_num;
	pci_set_drvdata(dev, card);

	return 0;
}

/*
 * Unregisters a TurboPAM board by releasing all its ressources (irq, mem etc).
 *
 * 	card: the board.
 */
static void __devexit tpam_unregister_card(tpam_card *card) {
	isdn_ctrl cmd;

	/* prevent the ISDN link layer that the driver will be unloaded */
	cmd.command = ISDN_STAT_UNLOAD;
	cmd.driver = card->id;
	(* card->interface.statcallb)(&cmd);

	/* release interrupt */
	free_irq(card->irq, card);

	/* release mapped memory */
	iounmap((void *)card->bar0);
}

/*
 * Stops the driver.
 */
static void __devexit tpam_remove(struct pci_dev *pcidev) {
	tpam_card *card = pci_get_drvdata(pcidev);
	tpam_card *c;

	/* remove from the list of cards */
	if (card == cards)
		cards = cards->next;
	else {
		c = cards;
		while (c->next != card) 
			c = c->next;
		c->next = c->next->next;
	}
	
	/* unregister each board */
	tpam_unregister_card(card);
	
	/* and free the board structure itself */
	kfree(card);
}

static struct pci_device_id tpam_pci_tbl[] __devinitdata = {
	{ PCI_VENDOR_ID_XILINX, PCI_DEVICE_ID_TURBOPAM,
	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ }
};

MODULE_DEVICE_TABLE(pci, tpam_pci_tbl);

static struct pci_driver tpam_driver = {
	name:		"tpam",
	id_table:	tpam_pci_tbl,
	probe:		tpam_probe,
	remove:		tpam_remove,
};

static int __init tpam_init(void) {
	int ret;
	
	ret = pci_module_init(&tpam_driver);
	if (ret)
		return ret;
	printk(KERN_INFO "TurboPAM: %d card%s found, driver loaded.\n", 
	       cards_num, (cards_num > 1) ? "s" : "");
	return 0;
}

static void __exit tpam_exit(void) {
	pci_unregister_driver(&tpam_driver);
	printk(KERN_INFO "TurboPAM: driver unloaded\n");
}

/* Module entry points */
module_init(tpam_init);
module_exit(tpam_exit);

