1045 lines
26 KiB
C
1045 lines
26 KiB
C
/*
|
|
* Copyright 2003 Digi International (www.digi.com)
|
|
* Scott H Kilau <Scott_Kilau at digi dot com>
|
|
*
|
|
* 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, EXPRESS OR IMPLIED; 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.
|
|
*
|
|
*
|
|
* NOTE TO LINUX KERNEL HACKERS: DO NOT REFORMAT THIS CODE!
|
|
*
|
|
* This is shared code between Digi's CVS archive and the
|
|
* Linux Kernel sources.
|
|
* Changing the source just for reformatting needlessly breaks
|
|
* our CVS diff history.
|
|
*
|
|
* Send any bug fixes/changes to: Eng.Linux at digi dot com.
|
|
* Thank you.
|
|
*
|
|
* $Id: dgap_driver.c,v 1.3 2011/06/21 10:35:16 markh Exp $
|
|
*/
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/delay.h> /* For udelay */
|
|
#include <linux/slab.h>
|
|
#include <asm/uaccess.h> /* For copy_from_user/copy_to_user */
|
|
#include <linux/sched.h>
|
|
|
|
#include "dgap_driver.h"
|
|
#include "dgap_pci.h"
|
|
#include "dgap_fep5.h"
|
|
#include "dgap_tty.h"
|
|
#include "dgap_conf.h"
|
|
#include "dgap_parse.h"
|
|
#include "dgap_trace.h"
|
|
#include "dgap_sysfs.h"
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Digi International, http://www.digi.com");
|
|
MODULE_DESCRIPTION("Driver for the Digi International EPCA PCI based product line");
|
|
MODULE_SUPPORTED_DEVICE("dgap");
|
|
|
|
/*
|
|
* insmod command line overrideable parameters
|
|
*
|
|
* NOTE: we use a set of macros to create the variables, which allows
|
|
* us to specify the variable type, name, initial value, and description.
|
|
*/
|
|
PARM_INT(debug, 0x00, 0644, "Driver debugging level");
|
|
PARM_INT(rawreadok, 1, 0644, "Bypass flip buffers on input");
|
|
PARM_INT(trcbuf_size, 0x100000, 0644, "Debugging trace buffer size.");
|
|
|
|
|
|
/**************************************************************************
|
|
*
|
|
* protos for this file
|
|
*
|
|
*/
|
|
|
|
static int dgap_start(void);
|
|
static void dgap_init_globals(void);
|
|
static int dgap_found_board(struct pci_dev *pdev, int id);
|
|
static void dgap_cleanup_board(struct board_t *brd);
|
|
static void dgap_poll_handler(ulong dummy);
|
|
static int dgap_init_pci(void);
|
|
static int dgap_init_one(struct pci_dev *pdev, const struct pci_device_id *ent);
|
|
static void dgap_remove_one(struct pci_dev *dev);
|
|
static int dgap_probe1(struct pci_dev *pdev, int card_type);
|
|
static void dgap_mbuf(struct board_t *brd, const char *fmt, ...);
|
|
static int dgap_do_remap(struct board_t *brd);
|
|
static irqreturn_t dgap_intr(int irq, void *voidbrd);
|
|
|
|
/* Driver load/unload functions */
|
|
int dgap_init_module(void);
|
|
void dgap_cleanup_module(void);
|
|
|
|
module_init(dgap_init_module);
|
|
module_exit(dgap_cleanup_module);
|
|
|
|
|
|
/*
|
|
* File operations permitted on Control/Management major.
|
|
*/
|
|
static struct file_operations DgapBoardFops =
|
|
{
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
uint dgap_NumBoards;
|
|
struct board_t *dgap_Board[MAXBOARDS];
|
|
DEFINE_SPINLOCK(dgap_global_lock);
|
|
ulong dgap_poll_counter;
|
|
char *dgap_config_buf;
|
|
int dgap_driver_state = DRIVER_INITIALIZED;
|
|
DEFINE_SPINLOCK(dgap_dl_lock);
|
|
wait_queue_head_t dgap_dl_wait;
|
|
int dgap_dl_action;
|
|
int dgap_poll_tick = 20; /* Poll interval - 20 ms */
|
|
|
|
/*
|
|
* Static vars.
|
|
*/
|
|
static int dgap_Major_Control_Registered = FALSE;
|
|
static uint dgap_driver_start = FALSE;
|
|
|
|
static struct class * dgap_class;
|
|
|
|
/*
|
|
* Poller stuff
|
|
*/
|
|
static DEFINE_SPINLOCK(dgap_poll_lock); /* Poll scheduling lock */
|
|
static ulong dgap_poll_time; /* Time of next poll */
|
|
static uint dgap_poll_stop; /* Used to tell poller to stop */
|
|
static struct timer_list dgap_poll_timer;
|
|
|
|
|
|
static struct pci_device_id dgap_pci_tbl[] = {
|
|
{ DIGI_VID, PCI_DEVICE_XEM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
|
|
{ DIGI_VID, PCI_DEVICE_CX_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 1 },
|
|
{ DIGI_VID, PCI_DEVICE_CX_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 2 },
|
|
{ DIGI_VID, PCI_DEVICE_EPCJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 3 },
|
|
{ DIGI_VID, PCI_DEVICE_920_2_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 4 },
|
|
{ DIGI_VID, PCI_DEVICE_920_4_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 5 },
|
|
{ DIGI_VID, PCI_DEVICE_920_8_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 6 },
|
|
{ DIGI_VID, PCI_DEVICE_XR_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 7 },
|
|
{ DIGI_VID, PCI_DEVICE_XRJ_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 8 },
|
|
{ DIGI_VID, PCI_DEVICE_XR_422_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 9 },
|
|
{ DIGI_VID, PCI_DEVICE_XR_IBM_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 10 },
|
|
{ DIGI_VID, PCI_DEVICE_XR_SAIP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 11 },
|
|
{ DIGI_VID, PCI_DEVICE_XR_BULL_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 12 },
|
|
{ DIGI_VID, PCI_DEVICE_920_8_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 13 },
|
|
{ DIGI_VID, PCI_DEVICE_XEM_HP_DID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 14 },
|
|
{0,} /* 0 terminated list. */
|
|
};
|
|
MODULE_DEVICE_TABLE(pci, dgap_pci_tbl);
|
|
|
|
|
|
/*
|
|
* A generic list of Product names, PCI Vendor ID, and PCI Device ID.
|
|
*/
|
|
struct board_id {
|
|
uint config_type;
|
|
uchar *name;
|
|
uint maxports;
|
|
uint dpatype;
|
|
};
|
|
|
|
static struct board_id dgap_Ids[] =
|
|
{
|
|
{ PPCM, PCI_DEVICE_XEM_NAME, 64, (T_PCXM | T_PCLITE | T_PCIBUS) },
|
|
{ PCX, PCI_DEVICE_CX_NAME, 128, (T_CX | T_PCIBUS) },
|
|
{ PCX, PCI_DEVICE_CX_IBM_NAME, 128, (T_CX | T_PCIBUS) },
|
|
{ PEPC, PCI_DEVICE_EPCJ_NAME, 224, (T_EPC | T_PCIBUS) },
|
|
{ APORT2_920P, PCI_DEVICE_920_2_NAME, 2, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ APORT4_920P, PCI_DEVICE_920_4_NAME, 4, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ APORT8_920P, PCI_DEVICE_920_8_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XR_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XRJ_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XR_422_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XR_IBM_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XR_SAIP_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PAPORT8, PCI_DEVICE_XR_BULL_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ APORT8_920P, PCI_DEVICE_920_8_HP_NAME, 8, (T_PCXR | T_PCLITE | T_PCIBUS) },
|
|
{ PPCM, PCI_DEVICE_XEM_HP_NAME, 64, (T_PCXM | T_PCLITE | T_PCIBUS) },
|
|
{0,} /* 0 terminated list. */
|
|
};
|
|
|
|
static struct pci_driver dgap_driver = {
|
|
.name = "dgap",
|
|
.probe = dgap_init_one,
|
|
.id_table = dgap_pci_tbl,
|
|
.remove = dgap_remove_one,
|
|
};
|
|
|
|
|
|
char *dgap_state_text[] = {
|
|
"Board Failed",
|
|
"Configuration for board not found.\n\t\t\tRun mpi to configure board.",
|
|
"Board Found",
|
|
"Need Reset",
|
|
"Finished Reset",
|
|
"Need Config",
|
|
"Finished Config",
|
|
"Need Device Creation",
|
|
"Requested Device Creation",
|
|
"Finished Device Creation",
|
|
"Need BIOS Load",
|
|
"Requested BIOS",
|
|
"Doing BIOS Load",
|
|
"Finished BIOS Load",
|
|
"Need FEP Load",
|
|
"Requested FEP",
|
|
"Doing FEP Load",
|
|
"Finished FEP Load",
|
|
"Requested PROC creation",
|
|
"Finished PROC creation",
|
|
"Board READY",
|
|
};
|
|
|
|
char *dgap_driver_state_text[] = {
|
|
"Driver Initialized",
|
|
"Driver needs configuration load.",
|
|
"Driver requested configuration from download daemon.",
|
|
"Driver Ready."
|
|
};
|
|
|
|
|
|
|
|
/************************************************************************
|
|
*
|
|
* Driver load/unload functions
|
|
*
|
|
************************************************************************/
|
|
|
|
/*
|
|
* init_module()
|
|
*
|
|
* Module load. This is where it all starts.
|
|
*/
|
|
int dgap_init_module(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
APR(("%s, Digi International Part Number %s\n", DG_NAME, DG_PART));
|
|
|
|
/*
|
|
* Initialize global stuff
|
|
*/
|
|
rc = dgap_start();
|
|
|
|
if (rc < 0) {
|
|
return(rc);
|
|
}
|
|
|
|
/*
|
|
* Find and configure all the cards
|
|
*/
|
|
rc = dgap_init_pci();
|
|
|
|
/*
|
|
* If something went wrong in the scan, bail out of driver.
|
|
*/
|
|
if (rc < 0) {
|
|
/* Only unregister the pci driver if it was actually registered. */
|
|
if (dgap_NumBoards)
|
|
pci_unregister_driver(&dgap_driver);
|
|
else
|
|
printk("WARNING: dgap driver load failed. No DGAP boards found.\n");
|
|
|
|
dgap_cleanup_module();
|
|
}
|
|
else {
|
|
dgap_create_driver_sysfiles(&dgap_driver);
|
|
}
|
|
|
|
DPR_INIT(("Finished init_module. Returning %d\n", rc));
|
|
return (rc);
|
|
}
|
|
|
|
|
|
/*
|
|
* Start of driver.
|
|
*/
|
|
static int dgap_start(void)
|
|
{
|
|
int rc = 0;
|
|
unsigned long flags;
|
|
|
|
if (dgap_driver_start == FALSE) {
|
|
|
|
dgap_driver_start = TRUE;
|
|
|
|
/* make sure that the globals are init'd before we do anything else */
|
|
dgap_init_globals();
|
|
|
|
dgap_NumBoards = 0;
|
|
|
|
APR(("For the tools package or updated drivers please visit http://www.digi.com\n"));
|
|
|
|
/*
|
|
* Register our base character device into the kernel.
|
|
* This allows the download daemon to connect to the downld device
|
|
* before any of the boards are init'ed.
|
|
*/
|
|
if (!dgap_Major_Control_Registered) {
|
|
/*
|
|
* Register management/dpa devices
|
|
*/
|
|
rc = register_chrdev(DIGI_DGAP_MAJOR, "dgap", &DgapBoardFops);
|
|
if (rc < 0) {
|
|
APR(("Can't register dgap driver device (%d)\n", rc));
|
|
return (rc);
|
|
}
|
|
|
|
dgap_class = class_create(THIS_MODULE, "dgap_mgmt");
|
|
device_create(dgap_class, NULL,
|
|
MKDEV(DIGI_DGAP_MAJOR, 0),
|
|
NULL, "dgap_mgmt");
|
|
device_create(dgap_class, NULL,
|
|
MKDEV(DIGI_DGAP_MAJOR, 1),
|
|
NULL, "dgap_downld");
|
|
dgap_Major_Control_Registered = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Init any global tty stuff.
|
|
*/
|
|
rc = dgap_tty_preinit();
|
|
|
|
if (rc < 0) {
|
|
APR(("tty preinit - not enough memory (%d)\n", rc));
|
|
return(rc);
|
|
}
|
|
|
|
/* Start the poller */
|
|
DGAP_LOCK(dgap_poll_lock, flags);
|
|
init_timer(&dgap_poll_timer);
|
|
dgap_poll_timer.function = dgap_poll_handler;
|
|
dgap_poll_timer.data = 0;
|
|
dgap_poll_time = jiffies + dgap_jiffies_from_ms(dgap_poll_tick);
|
|
dgap_poll_timer.expires = dgap_poll_time;
|
|
DGAP_UNLOCK(dgap_poll_lock, flags);
|
|
|
|
add_timer(&dgap_poll_timer);
|
|
|
|
dgap_driver_state = DRIVER_NEED_CONFIG_LOAD;
|
|
}
|
|
|
|
return (rc);
|
|
}
|
|
|
|
|
|
/*
|
|
* Register pci driver, and return how many boards we have.
|
|
*/
|
|
static int dgap_init_pci(void)
|
|
{
|
|
return pci_register_driver(&dgap_driver);
|
|
}
|
|
|
|
|
|
/* returns count (>= 0), or negative on error */
|
|
static int dgap_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
{
|
|
int rc;
|
|
|
|
/* wake up and enable device */
|
|
rc = pci_enable_device(pdev);
|
|
|
|
if (rc < 0) {
|
|
rc = -EIO;
|
|
} else {
|
|
rc = dgap_probe1(pdev, ent->driver_data);
|
|
if (rc == 0) {
|
|
dgap_NumBoards++;
|
|
DPR_INIT(("Incrementing numboards to %d\n", dgap_NumBoards));
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
|
|
static int dgap_probe1(struct pci_dev *pdev, int card_type)
|
|
{
|
|
return dgap_found_board(pdev, card_type);
|
|
}
|
|
|
|
|
|
static void dgap_remove_one(struct pci_dev *dev)
|
|
{
|
|
/* Do Nothing */
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_cleanup_module()
|
|
*
|
|
* Module unload. This is where it all ends.
|
|
*/
|
|
void dgap_cleanup_module(void)
|
|
{
|
|
int i;
|
|
ulong lock_flags;
|
|
|
|
DGAP_LOCK(dgap_poll_lock, lock_flags);
|
|
dgap_poll_stop = 1;
|
|
DGAP_UNLOCK(dgap_poll_lock, lock_flags);
|
|
|
|
/* Turn off poller right away. */
|
|
del_timer_sync( &dgap_poll_timer);
|
|
|
|
dgap_remove_driver_sysfiles(&dgap_driver);
|
|
|
|
|
|
if (dgap_Major_Control_Registered) {
|
|
device_destroy(dgap_class, MKDEV(DIGI_DGAP_MAJOR, 0));
|
|
device_destroy(dgap_class, MKDEV(DIGI_DGAP_MAJOR, 1));
|
|
class_destroy(dgap_class);
|
|
unregister_chrdev(DIGI_DGAP_MAJOR, "dgap");
|
|
}
|
|
|
|
kfree(dgap_config_buf);
|
|
|
|
for (i = 0; i < dgap_NumBoards; ++i) {
|
|
dgap_remove_ports_sysfiles(dgap_Board[i]);
|
|
dgap_tty_uninit(dgap_Board[i]);
|
|
dgap_cleanup_board(dgap_Board[i]);
|
|
}
|
|
|
|
dgap_tty_post_uninit();
|
|
|
|
#if defined(DGAP_TRACER)
|
|
/* last thing, make sure we release the tracebuffer */
|
|
dgap_tracer_free();
|
|
#endif
|
|
if (dgap_NumBoards)
|
|
pci_unregister_driver(&dgap_driver);
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_cleanup_board()
|
|
*
|
|
* Free all the memory associated with a board
|
|
*/
|
|
static void dgap_cleanup_board(struct board_t *brd)
|
|
{
|
|
int i = 0;
|
|
|
|
if(!brd || brd->magic != DGAP_BOARD_MAGIC)
|
|
return;
|
|
|
|
if (brd->intr_used && brd->irq)
|
|
free_irq(brd->irq, brd);
|
|
|
|
tasklet_kill(&brd->helper_tasklet);
|
|
|
|
if (brd->re_map_port) {
|
|
release_mem_region(brd->membase + 0x200000, 0x200000);
|
|
iounmap(brd->re_map_port);
|
|
brd->re_map_port = NULL;
|
|
}
|
|
|
|
if (brd->re_map_membase) {
|
|
release_mem_region(brd->membase, 0x200000);
|
|
iounmap(brd->re_map_membase);
|
|
brd->re_map_membase = NULL;
|
|
}
|
|
|
|
if (brd->msgbuf_head) {
|
|
unsigned long flags;
|
|
|
|
DGAP_LOCK(dgap_global_lock, flags);
|
|
brd->msgbuf = NULL;
|
|
printk("%s", brd->msgbuf_head);
|
|
kfree(brd->msgbuf_head);
|
|
brd->msgbuf_head = NULL;
|
|
DGAP_UNLOCK(dgap_global_lock, flags);
|
|
}
|
|
|
|
/* Free all allocated channels structs */
|
|
for (i = 0; i < MAXPORTS ; i++) {
|
|
if (brd->channels[i]) {
|
|
kfree(brd->channels[i]);
|
|
brd->channels[i] = NULL;
|
|
}
|
|
}
|
|
|
|
kfree(brd->flipbuf);
|
|
kfree(brd->flipflagbuf);
|
|
|
|
dgap_Board[brd->boardnum] = NULL;
|
|
|
|
kfree(brd);
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_found_board()
|
|
*
|
|
* A board has been found, init it.
|
|
*/
|
|
static int dgap_found_board(struct pci_dev *pdev, int id)
|
|
{
|
|
struct board_t *brd;
|
|
unsigned int pci_irq;
|
|
int i = 0;
|
|
unsigned long flags;
|
|
|
|
/* get the board structure and prep it */
|
|
brd = dgap_Board[dgap_NumBoards] =
|
|
(struct board_t *) dgap_driver_kzmalloc(sizeof(struct board_t), GFP_KERNEL);
|
|
if (!brd) {
|
|
APR(("memory allocation for board structure failed\n"));
|
|
return(-ENOMEM);
|
|
}
|
|
|
|
/* make a temporary message buffer for the boot messages */
|
|
brd->msgbuf = brd->msgbuf_head =
|
|
(char *) dgap_driver_kzmalloc(sizeof(char) * 8192, GFP_KERNEL);
|
|
if(!brd->msgbuf) {
|
|
kfree(brd);
|
|
APR(("memory allocation for board msgbuf failed\n"));
|
|
return(-ENOMEM);
|
|
}
|
|
|
|
/* store the info for the board we've found */
|
|
brd->magic = DGAP_BOARD_MAGIC;
|
|
brd->boardnum = dgap_NumBoards;
|
|
brd->firstminor = 0;
|
|
brd->vendor = dgap_pci_tbl[id].vendor;
|
|
brd->device = dgap_pci_tbl[id].device;
|
|
brd->pdev = pdev;
|
|
brd->pci_bus = pdev->bus->number;
|
|
brd->pci_slot = PCI_SLOT(pdev->devfn);
|
|
brd->name = dgap_Ids[id].name;
|
|
brd->maxports = dgap_Ids[id].maxports;
|
|
brd->type = dgap_Ids[id].config_type;
|
|
brd->dpatype = dgap_Ids[id].dpatype;
|
|
brd->dpastatus = BD_NOFEP;
|
|
init_waitqueue_head(&brd->state_wait);
|
|
|
|
DGAP_SPINLOCK_INIT(brd->bd_lock);
|
|
|
|
brd->state = BOARD_FOUND;
|
|
brd->runwait = 0;
|
|
brd->inhibit_poller = FALSE;
|
|
brd->wait_for_bios = 0;
|
|
brd->wait_for_fep = 0;
|
|
|
|
for (i = 0; i < MAXPORTS; i++) {
|
|
brd->channels[i] = NULL;
|
|
}
|
|
|
|
/* store which card & revision we have */
|
|
pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &brd->subvendor);
|
|
pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &brd->subdevice);
|
|
pci_read_config_byte(pdev, PCI_REVISION_ID, &brd->rev);
|
|
|
|
pci_irq = pdev->irq;
|
|
brd->irq = pci_irq;
|
|
|
|
/* get the PCI Base Address Registers */
|
|
|
|
/* Xr Jupiter and EPC use BAR 2 */
|
|
if (brd->device == PCI_DEVICE_XRJ_DID || brd->device == PCI_DEVICE_EPCJ_DID) {
|
|
brd->membase = pci_resource_start(pdev, 2);
|
|
brd->membase_end = pci_resource_end(pdev, 2);
|
|
}
|
|
/* Everyone else uses BAR 0 */
|
|
else {
|
|
brd->membase = pci_resource_start(pdev, 0);
|
|
brd->membase_end = pci_resource_end(pdev, 0);
|
|
}
|
|
|
|
if (!brd->membase) {
|
|
APR(("card has no PCI IO resources, failing board.\n"));
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (brd->membase & 1)
|
|
brd->membase &= ~3;
|
|
else
|
|
brd->membase &= ~15;
|
|
|
|
/*
|
|
* On the PCI boards, there is no IO space allocated
|
|
* The I/O registers will be in the first 3 bytes of the
|
|
* upper 2MB of the 4MB memory space. The board memory
|
|
* will be mapped into the low 2MB of the 4MB memory space
|
|
*/
|
|
brd->port = brd->membase + PCI_IO_OFFSET;
|
|
brd->port_end = brd->port + PCI_IO_SIZE;
|
|
|
|
|
|
/*
|
|
* Special initialization for non-PLX boards
|
|
*/
|
|
if (brd->device != PCI_DEVICE_XRJ_DID && brd->device != PCI_DEVICE_EPCJ_DID) {
|
|
unsigned short cmd;
|
|
|
|
pci_write_config_byte(pdev, 0x40, 0);
|
|
pci_write_config_byte(pdev, 0x46, 0);
|
|
|
|
/* Limit burst length to 2 doubleword transactions */
|
|
pci_write_config_byte(pdev, 0x42, 1);
|
|
|
|
/*
|
|
* Enable IO and mem if not already done.
|
|
* This was needed for support on Itanium.
|
|
*/
|
|
pci_read_config_word(pdev, PCI_COMMAND, &cmd);
|
|
cmd |= (PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
|
|
pci_write_config_word(pdev, PCI_COMMAND, cmd);
|
|
}
|
|
|
|
/* init our poll helper tasklet */
|
|
tasklet_init(&brd->helper_tasklet, dgap_poll_tasklet, (unsigned long) brd);
|
|
|
|
/* Log the information about the board */
|
|
dgap_mbuf(brd, DRVSTR": board %d: %s (rev %d), irq %d\n",
|
|
dgap_NumBoards, brd->name, brd->rev, brd->irq);
|
|
|
|
DPR_INIT(("dgap_scan(%d) - printing out the msgbuf\n", i));
|
|
DGAP_LOCK(dgap_global_lock, flags);
|
|
brd->msgbuf = NULL;
|
|
printk("%s", brd->msgbuf_head);
|
|
kfree(brd->msgbuf_head);
|
|
brd->msgbuf_head = NULL;
|
|
DGAP_UNLOCK(dgap_global_lock, flags);
|
|
|
|
i = dgap_do_remap(brd);
|
|
if (i)
|
|
brd->state = BOARD_FAILED;
|
|
else
|
|
brd->state = NEED_RESET;
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
int dgap_finalize_board_init(struct board_t *brd) {
|
|
|
|
int rc;
|
|
|
|
DPR_INIT(("dgap_finalize_board_init() - start\n"));
|
|
|
|
if (!brd || brd->magic != DGAP_BOARD_MAGIC)
|
|
return(-ENODEV);
|
|
|
|
DPR_INIT(("dgap_finalize_board_init() - start #2\n"));
|
|
|
|
brd->use_interrupts = dgap_config_get_useintr(brd);
|
|
|
|
/*
|
|
* Set up our interrupt handler if we are set to do interrupts.
|
|
*/
|
|
if (brd->use_interrupts && brd->irq) {
|
|
|
|
rc = request_irq(brd->irq, dgap_intr, IRQF_SHARED, "DGAP", brd);
|
|
|
|
if (rc) {
|
|
dgap_mbuf(brd, DRVSTR": Failed to hook IRQ %d. Board will work in poll mode.\n",
|
|
brd->irq);
|
|
brd->intr_used = 0;
|
|
}
|
|
else
|
|
brd->intr_used = 1;
|
|
} else {
|
|
brd->intr_used = 0;
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Remap PCI memory.
|
|
*/
|
|
static int dgap_do_remap(struct board_t *brd)
|
|
{
|
|
if (!brd || brd->magic != DGAP_BOARD_MAGIC)
|
|
return -ENXIO;
|
|
|
|
if (!request_mem_region(brd->membase, 0x200000, "dgap")) {
|
|
APR(("dgap: mem_region %lx already in use.\n", brd->membase));
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!request_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000, "dgap")) {
|
|
APR(("dgap: mem_region IO %lx already in use.\n",
|
|
brd->membase + PCI_IO_OFFSET));
|
|
release_mem_region(brd->membase, 0x200000);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
brd->re_map_membase = ioremap(brd->membase, 0x200000);
|
|
if (!brd->re_map_membase) {
|
|
APR(("dgap: ioremap mem %lx cannot be mapped.\n", brd->membase));
|
|
release_mem_region(brd->membase, 0x200000);
|
|
release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
brd->re_map_port = ioremap((brd->membase + PCI_IO_OFFSET), 0x200000);
|
|
if (!brd->re_map_port) {
|
|
release_mem_region(brd->membase, 0x200000);
|
|
release_mem_region(brd->membase + PCI_IO_OFFSET, 0x200000);
|
|
iounmap(brd->re_map_membase);
|
|
APR(("dgap: ioremap IO mem %lx cannot be mapped.\n",
|
|
brd->membase + PCI_IO_OFFSET));
|
|
return -ENOMEM;
|
|
}
|
|
|
|
DPR_INIT(("remapped io: 0x%p remapped mem: 0x%p\n",
|
|
brd->re_map_port, brd->re_map_membase));
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Function:
|
|
*
|
|
* dgap_poll_handler
|
|
*
|
|
* Author:
|
|
*
|
|
* Scott H Kilau
|
|
*
|
|
* Parameters:
|
|
*
|
|
* dummy -- ignored
|
|
*
|
|
* Return Values:
|
|
*
|
|
* none
|
|
*
|
|
* Description:
|
|
*
|
|
* As each timer expires, it determines (a) whether the "transmit"
|
|
* waiter needs to be woken up, and (b) whether the poller needs to
|
|
* be rescheduled.
|
|
*
|
|
******************************************************************************/
|
|
|
|
static void dgap_poll_handler(ulong dummy)
|
|
{
|
|
int i;
|
|
struct board_t *brd;
|
|
unsigned long lock_flags;
|
|
unsigned long lock_flags2;
|
|
ulong new_time;
|
|
|
|
dgap_poll_counter++;
|
|
|
|
|
|
/*
|
|
* If driver needs the config file still,
|
|
* keep trying to wake up the downloader to
|
|
* send us the file.
|
|
*/
|
|
if (dgap_driver_state == DRIVER_NEED_CONFIG_LOAD) {
|
|
/*
|
|
* Signal downloader, its got some work to do.
|
|
*/
|
|
DGAP_LOCK(dgap_dl_lock, lock_flags2);
|
|
if (dgap_dl_action != 1) {
|
|
dgap_dl_action = 1;
|
|
wake_up_interruptible(&dgap_dl_wait);
|
|
}
|
|
DGAP_UNLOCK(dgap_dl_lock, lock_flags2);
|
|
goto schedule_poller;
|
|
}
|
|
/*
|
|
* Do not start the board state machine until
|
|
* driver tells us its up and running, and has
|
|
* everything it needs.
|
|
*/
|
|
else if (dgap_driver_state != DRIVER_READY) {
|
|
goto schedule_poller;
|
|
}
|
|
|
|
/*
|
|
* If we have just 1 board, or the system is not SMP,
|
|
* then use the typical old style poller.
|
|
* Otherwise, use our new tasklet based poller, which should
|
|
* speed things up for multiple boards.
|
|
*/
|
|
if ( (dgap_NumBoards == 1) || (num_online_cpus() <= 1) ) {
|
|
for (i = 0; i < dgap_NumBoards; i++) {
|
|
|
|
brd = dgap_Board[i];
|
|
|
|
if (brd->state == BOARD_FAILED) {
|
|
continue;
|
|
}
|
|
if (!brd->intr_running) {
|
|
/* Call the real board poller directly */
|
|
dgap_poll_tasklet((unsigned long) brd);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Go thru each board, kicking off a tasklet for each if needed */
|
|
for (i = 0; i < dgap_NumBoards; i++) {
|
|
brd = dgap_Board[i];
|
|
|
|
/*
|
|
* Attempt to grab the board lock.
|
|
*
|
|
* If we can't get it, no big deal, the next poll will get it.
|
|
* Basically, I just really don't want to spin in here, because I want
|
|
* to kick off my tasklets as fast as I can, and then get out the poller.
|
|
*/
|
|
if (!spin_trylock(&brd->bd_lock)) {
|
|
continue;
|
|
}
|
|
|
|
/* If board is in a failed state, don't bother scheduling a tasklet */
|
|
if (brd->state == BOARD_FAILED) {
|
|
spin_unlock(&brd->bd_lock);
|
|
continue;
|
|
}
|
|
|
|
/* Schedule a poll helper task */
|
|
if (!brd->intr_running) {
|
|
tasklet_schedule(&brd->helper_tasklet);
|
|
}
|
|
|
|
/*
|
|
* Can't do DGAP_UNLOCK here, as we don't have
|
|
* lock_flags because we did a trylock above.
|
|
*/
|
|
spin_unlock(&brd->bd_lock);
|
|
}
|
|
}
|
|
|
|
schedule_poller:
|
|
|
|
/*
|
|
* Schedule ourself back at the nominal wakeup interval.
|
|
*/
|
|
DGAP_LOCK(dgap_poll_lock, lock_flags );
|
|
dgap_poll_time += dgap_jiffies_from_ms(dgap_poll_tick);
|
|
|
|
new_time = dgap_poll_time - jiffies;
|
|
|
|
if ((ulong) new_time >= 2 * dgap_poll_tick) {
|
|
dgap_poll_time = jiffies + dgap_jiffies_from_ms(dgap_poll_tick);
|
|
}
|
|
|
|
dgap_poll_timer.function = dgap_poll_handler;
|
|
dgap_poll_timer.data = 0;
|
|
dgap_poll_timer.expires = dgap_poll_time;
|
|
DGAP_UNLOCK(dgap_poll_lock, lock_flags );
|
|
|
|
if (!dgap_poll_stop)
|
|
add_timer(&dgap_poll_timer);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* dgap_intr()
|
|
*
|
|
* Driver interrupt handler.
|
|
*/
|
|
static irqreturn_t dgap_intr(int irq, void *voidbrd)
|
|
{
|
|
struct board_t *brd = (struct board_t *) voidbrd;
|
|
|
|
if (!brd) {
|
|
APR(("Received interrupt (%d) with null board associated\n", irq));
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
/*
|
|
* Check to make sure its for us.
|
|
*/
|
|
if (brd->magic != DGAP_BOARD_MAGIC) {
|
|
APR(("Received interrupt (%d) with a board pointer that wasn't ours!\n", irq));
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
brd->intr_count++;
|
|
|
|
/*
|
|
* Schedule tasklet to run at a better time.
|
|
*/
|
|
tasklet_schedule(&brd->helper_tasklet);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_init_globals()
|
|
*
|
|
* This is where we initialize the globals from the static insmod
|
|
* configuration variables. These are declared near the head of
|
|
* this file.
|
|
*/
|
|
static void dgap_init_globals(void)
|
|
{
|
|
int i = 0;
|
|
|
|
dgap_rawreadok = rawreadok;
|
|
dgap_trcbuf_size = trcbuf_size;
|
|
dgap_debug = debug;
|
|
|
|
for (i = 0; i < MAXBOARDS; i++) {
|
|
dgap_Board[i] = NULL;
|
|
}
|
|
|
|
init_timer( &dgap_poll_timer );
|
|
|
|
init_waitqueue_head(&dgap_dl_wait);
|
|
dgap_dl_action = 0;
|
|
}
|
|
|
|
|
|
/************************************************************************
|
|
*
|
|
* Utility functions
|
|
*
|
|
************************************************************************/
|
|
|
|
|
|
/*
|
|
* dgap_driver_kzmalloc()
|
|
*
|
|
* Malloc and clear memory,
|
|
*/
|
|
void *dgap_driver_kzmalloc(size_t size, int priority)
|
|
{
|
|
void *p = kmalloc(size, priority);
|
|
if(p)
|
|
memset(p, 0, size);
|
|
return(p);
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_mbuf()
|
|
*
|
|
* Used to print to the message buffer during board init.
|
|
*/
|
|
static void dgap_mbuf(struct board_t *brd, const char *fmt, ...) {
|
|
va_list ap;
|
|
char buf[1024];
|
|
int i;
|
|
unsigned long flags;
|
|
size_t length;
|
|
|
|
DGAP_LOCK(dgap_global_lock, flags);
|
|
|
|
/* Format buf using fmt and arguments contained in ap. */
|
|
va_start(ap, fmt);
|
|
i = vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
DPR((buf));
|
|
|
|
if (!brd || !brd->msgbuf) {
|
|
printk("%s", buf);
|
|
DGAP_UNLOCK(dgap_global_lock, flags);
|
|
return;
|
|
}
|
|
|
|
length = strlen(buf) + 1;
|
|
if (brd->msgbuf - brd->msgbuf_head < length)
|
|
length = brd->msgbuf - brd->msgbuf_head;
|
|
memcpy(brd->msgbuf, buf, length);
|
|
brd->msgbuf += length;
|
|
|
|
DGAP_UNLOCK(dgap_global_lock, flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* dgap_ms_sleep()
|
|
*
|
|
* Put the driver to sleep for x ms's
|
|
*
|
|
* Returns 0 if timed out, !0 (showing signal) if interrupted by a signal.
|
|
*/
|
|
int dgap_ms_sleep(ulong ms)
|
|
{
|
|
current->state = TASK_INTERRUPTIBLE;
|
|
schedule_timeout((ms * HZ) / 1000);
|
|
return (signal_pending(current));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* dgap_ioctl_name() : Returns a text version of each ioctl value.
|
|
*/
|
|
char *dgap_ioctl_name(int cmd)
|
|
{
|
|
switch(cmd) {
|
|
|
|
case TCGETA: return("TCGETA");
|
|
case TCGETS: return("TCGETS");
|
|
case TCSETA: return("TCSETA");
|
|
case TCSETS: return("TCSETS");
|
|
case TCSETAW: return("TCSETAW");
|
|
case TCSETSW: return("TCSETSW");
|
|
case TCSETAF: return("TCSETAF");
|
|
case TCSETSF: return("TCSETSF");
|
|
case TCSBRK: return("TCSBRK");
|
|
case TCXONC: return("TCXONC");
|
|
case TCFLSH: return("TCFLSH");
|
|
case TIOCGSID: return("TIOCGSID");
|
|
|
|
case TIOCGETD: return("TIOCGETD");
|
|
case TIOCSETD: return("TIOCSETD");
|
|
case TIOCGWINSZ: return("TIOCGWINSZ");
|
|
case TIOCSWINSZ: return("TIOCSWINSZ");
|
|
|
|
case TIOCMGET: return("TIOCMGET");
|
|
case TIOCMSET: return("TIOCMSET");
|
|
case TIOCMBIS: return("TIOCMBIS");
|
|
case TIOCMBIC: return("TIOCMBIC");
|
|
|
|
/* from digi.h */
|
|
case DIGI_SETA: return("DIGI_SETA");
|
|
case DIGI_SETAW: return("DIGI_SETAW");
|
|
case DIGI_SETAF: return("DIGI_SETAF");
|
|
case DIGI_SETFLOW: return("DIGI_SETFLOW");
|
|
case DIGI_SETAFLOW: return("DIGI_SETAFLOW");
|
|
case DIGI_GETFLOW: return("DIGI_GETFLOW");
|
|
case DIGI_GETAFLOW: return("DIGI_GETAFLOW");
|
|
case DIGI_GETA: return("DIGI_GETA");
|
|
case DIGI_GEDELAY: return("DIGI_GEDELAY");
|
|
case DIGI_SEDELAY: return("DIGI_SEDELAY");
|
|
case DIGI_GETCUSTOMBAUD: return("DIGI_GETCUSTOMBAUD");
|
|
case DIGI_SETCUSTOMBAUD: return("DIGI_SETCUSTOMBAUD");
|
|
case TIOCMODG: return("TIOCMODG");
|
|
case TIOCMODS: return("TIOCMODS");
|
|
case TIOCSDTR: return("TIOCSDTR");
|
|
case TIOCCDTR: return("TIOCCDTR");
|
|
|
|
default: return("unknown");
|
|
}
|
|
}
|