2005-04-16 16:20:36 -06:00
|
|
|
/*
|
|
|
|
* Regular cardbus driver ("yenta_socket")
|
|
|
|
*
|
|
|
|
* (C) Copyright 1999, 2000 Linus Torvalds
|
|
|
|
*
|
|
|
|
* Changelog:
|
|
|
|
* Aug 2002: Manfred Spraul <manfred@colorfullife.com>
|
|
|
|
* Dynamically adjust the size of the bridge resource
|
|
|
|
*
|
|
|
|
* May 2003: Dominik Brodowski <linux@brodo.de>
|
|
|
|
* Merge pci_socket.c and yenta.c into one file
|
|
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/sched.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
|
|
|
|
#include <pcmcia/cs_types.h>
|
|
|
|
#include <pcmcia/ss.h>
|
|
|
|
#include <pcmcia/cs.h>
|
|
|
|
|
|
|
|
#include <asm/io.h>
|
|
|
|
|
|
|
|
#include "yenta_socket.h"
|
|
|
|
#include "i82365.h"
|
|
|
|
|
|
|
|
static int disable_clkrun;
|
|
|
|
module_param(disable_clkrun, bool, 0444);
|
|
|
|
MODULE_PARM_DESC(disable_clkrun, "If PC card doesn't function properly, please try this option");
|
|
|
|
|
2005-06-23 01:10:12 -06:00
|
|
|
static int isa_probe = 1;
|
|
|
|
module_param(isa_probe, bool, 0444);
|
|
|
|
MODULE_PARM_DESC(isa_probe, "If set ISA interrupts are probed (default). Set to N to disable probing");
|
|
|
|
|
|
|
|
static int pwr_irqs_off;
|
|
|
|
module_param(pwr_irqs_off, bool, 0644);
|
|
|
|
MODULE_PARM_DESC(pwr_irqs_off, "Force IRQs off during power-on of slot. Use only when seeing IRQ storms!");
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
#if 0
|
|
|
|
#define debug(x,args...) printk(KERN_DEBUG "%s: " x, __func__ , ##args)
|
|
|
|
#else
|
|
|
|
#define debug(x,args...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Don't ask.. */
|
|
|
|
#define to_cycles(ns) ((ns)/120)
|
|
|
|
#define to_ns(cycles) ((cycles)*120)
|
|
|
|
|
|
|
|
static int yenta_probe_cb_irq(struct yenta_socket *socket);
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned int override_bios;
|
|
|
|
module_param(override_bios, uint, 0000);
|
|
|
|
MODULE_PARM_DESC (override_bios, "yenta ignore bios resource allocation");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate easy-to-use ways of reading a cardbus sockets
|
|
|
|
* regular memory space ("cb_xxx"), configuration space
|
|
|
|
* ("config_xxx") and compatibility space ("exca_xxxx")
|
|
|
|
*/
|
|
|
|
static inline u32 cb_readl(struct yenta_socket *socket, unsigned reg)
|
|
|
|
{
|
|
|
|
u32 val = readl(socket->base + reg);
|
|
|
|
debug("%p %04x %08x\n", socket, reg, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void cb_writel(struct yenta_socket *socket, unsigned reg, u32 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %08x\n", socket, reg, val);
|
|
|
|
writel(val, socket->base + reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u8 config_readb(struct yenta_socket *socket, unsigned offset)
|
|
|
|
{
|
|
|
|
u8 val;
|
|
|
|
pci_read_config_byte(socket->dev, offset, &val);
|
|
|
|
debug("%p %04x %02x\n", socket, offset, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u16 config_readw(struct yenta_socket *socket, unsigned offset)
|
|
|
|
{
|
|
|
|
u16 val;
|
|
|
|
pci_read_config_word(socket->dev, offset, &val);
|
|
|
|
debug("%p %04x %04x\n", socket, offset, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u32 config_readl(struct yenta_socket *socket, unsigned offset)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
pci_read_config_dword(socket->dev, offset, &val);
|
|
|
|
debug("%p %04x %08x\n", socket, offset, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void config_writeb(struct yenta_socket *socket, unsigned offset, u8 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %02x\n", socket, offset, val);
|
|
|
|
pci_write_config_byte(socket->dev, offset, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void config_writew(struct yenta_socket *socket, unsigned offset, u16 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %04x\n", socket, offset, val);
|
|
|
|
pci_write_config_word(socket->dev, offset, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void config_writel(struct yenta_socket *socket, unsigned offset, u32 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %08x\n", socket, offset, val);
|
|
|
|
pci_write_config_dword(socket->dev, offset, val);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u8 exca_readb(struct yenta_socket *socket, unsigned reg)
|
|
|
|
{
|
|
|
|
u8 val = readb(socket->base + 0x800 + reg);
|
|
|
|
debug("%p %04x %02x\n", socket, reg, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u8 exca_readw(struct yenta_socket *socket, unsigned reg)
|
|
|
|
{
|
|
|
|
u16 val;
|
|
|
|
val = readb(socket->base + 0x800 + reg);
|
|
|
|
val |= readb(socket->base + 0x800 + reg + 1) << 8;
|
|
|
|
debug("%p %04x %04x\n", socket, reg, val);
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void exca_writeb(struct yenta_socket *socket, unsigned reg, u8 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %02x\n", socket, reg, val);
|
|
|
|
writeb(val, socket->base + 0x800 + reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void exca_writew(struct yenta_socket *socket, unsigned reg, u16 val)
|
|
|
|
{
|
|
|
|
debug("%p %04x %04x\n", socket, reg, val);
|
|
|
|
writeb(val, socket->base + 0x800 + reg);
|
|
|
|
writeb(val >> 8, socket->base + 0x800 + reg + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ugh, mixed-mode cardbus and 16-bit pccard state: things depend
|
|
|
|
* on what kind of card is inserted..
|
|
|
|
*/
|
|
|
|
static int yenta_get_status(struct pcmcia_socket *sock, unsigned int *value)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
unsigned int val;
|
|
|
|
u32 state = cb_readl(socket, CB_SOCKET_STATE);
|
|
|
|
|
|
|
|
val = (state & CB_3VCARD) ? SS_3VCARD : 0;
|
|
|
|
val |= (state & CB_XVCARD) ? SS_XVCARD : 0;
|
2005-06-23 01:10:12 -06:00
|
|
|
val |= (state & (CB_5VCARD | CB_3VCARD | CB_XVCARD | CB_YVCARD)) ? 0 : SS_PENDING;
|
|
|
|
val |= (state & (CB_CDETECT1 | CB_CDETECT2)) ? SS_PENDING : 0;
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (state & CB_CBCARD) {
|
|
|
|
val |= SS_CARDBUS;
|
|
|
|
val |= (state & CB_CARDSTS) ? SS_STSCHG : 0;
|
|
|
|
val |= (state & (CB_CDETECT1 | CB_CDETECT2)) ? 0 : SS_DETECT;
|
|
|
|
val |= (state & CB_PWRCYCLE) ? SS_POWERON | SS_READY : 0;
|
2005-06-23 01:10:12 -06:00
|
|
|
} else if (state & CB_16BITCARD) {
|
2005-04-16 16:20:36 -06:00
|
|
|
u8 status = exca_readb(socket, I365_STATUS);
|
|
|
|
val |= ((status & I365_CS_DETECT) == I365_CS_DETECT) ? SS_DETECT : 0;
|
|
|
|
if (exca_readb(socket, I365_INTCTL) & I365_PC_IOCARD) {
|
|
|
|
val |= (status & I365_CS_STSCHG) ? 0 : SS_STSCHG;
|
|
|
|
} else {
|
|
|
|
val |= (status & I365_CS_BVD1) ? 0 : SS_BATDEAD;
|
|
|
|
val |= (status & I365_CS_BVD2) ? 0 : SS_BATWARN;
|
|
|
|
}
|
|
|
|
val |= (status & I365_CS_WRPROT) ? SS_WRPROT : 0;
|
|
|
|
val |= (status & I365_CS_READY) ? SS_READY : 0;
|
|
|
|
val |= (status & I365_CS_POWERON) ? SS_POWERON : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
*value = val;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_Vcc_power(u32 control)
|
|
|
|
{
|
|
|
|
switch (control & CB_SC_VCC_MASK) {
|
|
|
|
case CB_SC_VCC_5V: return 50;
|
|
|
|
case CB_SC_VCC_3V: return 33;
|
|
|
|
default: return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_Vpp_power(u32 control)
|
|
|
|
{
|
|
|
|
switch (control & CB_SC_VPP_MASK) {
|
|
|
|
case CB_SC_VPP_12V: return 120;
|
|
|
|
case CB_SC_VPP_5V: return 50;
|
|
|
|
case CB_SC_VPP_3V: return 33;
|
|
|
|
default: return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_get_socket(struct pcmcia_socket *sock, socket_state_t *state)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
u8 reg;
|
|
|
|
u32 control;
|
|
|
|
|
|
|
|
control = cb_readl(socket, CB_SOCKET_CONTROL);
|
|
|
|
|
|
|
|
state->Vcc = yenta_Vcc_power(control);
|
|
|
|
state->Vpp = yenta_Vpp_power(control);
|
|
|
|
state->io_irq = socket->io_irq;
|
|
|
|
|
|
|
|
if (cb_readl(socket, CB_SOCKET_STATE) & CB_CBCARD) {
|
|
|
|
u16 bridge = config_readw(socket, CB_BRIDGE_CONTROL);
|
|
|
|
if (bridge & CB_BRIDGE_CRST)
|
|
|
|
state->flags |= SS_RESET;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 16-bit card state.. */
|
|
|
|
reg = exca_readb(socket, I365_POWER);
|
|
|
|
state->flags = (reg & I365_PWR_AUTO) ? SS_PWR_AUTO : 0;
|
|
|
|
state->flags |= (reg & I365_PWR_OUT) ? SS_OUTPUT_ENA : 0;
|
|
|
|
|
|
|
|
reg = exca_readb(socket, I365_INTCTL);
|
|
|
|
state->flags |= (reg & I365_PC_RESET) ? 0 : SS_RESET;
|
|
|
|
state->flags |= (reg & I365_PC_IOCARD) ? SS_IOCARD : 0;
|
|
|
|
|
|
|
|
reg = exca_readb(socket, I365_CSCINT);
|
|
|
|
state->csc_mask = (reg & I365_CSC_DETECT) ? SS_DETECT : 0;
|
|
|
|
if (state->flags & SS_IOCARD) {
|
|
|
|
state->csc_mask |= (reg & I365_CSC_STSCHG) ? SS_STSCHG : 0;
|
|
|
|
} else {
|
|
|
|
state->csc_mask |= (reg & I365_CSC_BVD1) ? SS_BATDEAD : 0;
|
|
|
|
state->csc_mask |= (reg & I365_CSC_BVD2) ? SS_BATWARN : 0;
|
|
|
|
state->csc_mask |= (reg & I365_CSC_READY) ? SS_READY : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void yenta_set_power(struct yenta_socket *socket, socket_state_t *state)
|
|
|
|
{
|
|
|
|
u32 reg = 0; /* CB_SC_STPCLK? */
|
|
|
|
switch (state->Vcc) {
|
|
|
|
case 33: reg = CB_SC_VCC_3V; break;
|
|
|
|
case 50: reg = CB_SC_VCC_5V; break;
|
|
|
|
default: reg = 0; break;
|
|
|
|
}
|
|
|
|
switch (state->Vpp) {
|
|
|
|
case 33: reg |= CB_SC_VPP_3V; break;
|
|
|
|
case 50: reg |= CB_SC_VPP_5V; break;
|
|
|
|
case 120: reg |= CB_SC_VPP_12V; break;
|
|
|
|
}
|
|
|
|
if (reg != cb_readl(socket, CB_SOCKET_CONTROL))
|
|
|
|
cb_writel(socket, CB_SOCKET_CONTROL, reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_set_socket(struct pcmcia_socket *sock, socket_state_t *state)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
u16 bridge;
|
|
|
|
|
|
|
|
yenta_set_power(socket, state);
|
|
|
|
socket->io_irq = state->io_irq;
|
|
|
|
bridge = config_readw(socket, CB_BRIDGE_CONTROL) & ~(CB_BRIDGE_CRST | CB_BRIDGE_INTR);
|
|
|
|
if (cb_readl(socket, CB_SOCKET_STATE) & CB_CBCARD) {
|
|
|
|
u8 intr;
|
|
|
|
bridge |= (state->flags & SS_RESET) ? CB_BRIDGE_CRST : 0;
|
|
|
|
|
|
|
|
/* ISA interrupt control? */
|
|
|
|
intr = exca_readb(socket, I365_INTCTL);
|
|
|
|
intr = (intr & ~0xf);
|
|
|
|
if (!socket->cb_irq) {
|
|
|
|
intr |= state->io_irq;
|
|
|
|
bridge |= CB_BRIDGE_INTR;
|
|
|
|
}
|
|
|
|
exca_writeb(socket, I365_INTCTL, intr);
|
|
|
|
} else {
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
reg = exca_readb(socket, I365_INTCTL) & (I365_RING_ENA | I365_INTR_ENA);
|
|
|
|
reg |= (state->flags & SS_RESET) ? 0 : I365_PC_RESET;
|
|
|
|
reg |= (state->flags & SS_IOCARD) ? I365_PC_IOCARD : 0;
|
|
|
|
if (state->io_irq != socket->cb_irq) {
|
|
|
|
reg |= state->io_irq;
|
|
|
|
bridge |= CB_BRIDGE_INTR;
|
|
|
|
}
|
|
|
|
exca_writeb(socket, I365_INTCTL, reg);
|
|
|
|
|
|
|
|
reg = exca_readb(socket, I365_POWER) & (I365_VCC_MASK|I365_VPP1_MASK);
|
|
|
|
reg |= I365_PWR_NORESET;
|
|
|
|
if (state->flags & SS_PWR_AUTO) reg |= I365_PWR_AUTO;
|
|
|
|
if (state->flags & SS_OUTPUT_ENA) reg |= I365_PWR_OUT;
|
|
|
|
if (exca_readb(socket, I365_POWER) != reg)
|
|
|
|
exca_writeb(socket, I365_POWER, reg);
|
|
|
|
|
|
|
|
/* CSC interrupt: no ISA irq for CSC */
|
|
|
|
reg = I365_CSC_DETECT;
|
|
|
|
if (state->flags & SS_IOCARD) {
|
|
|
|
if (state->csc_mask & SS_STSCHG) reg |= I365_CSC_STSCHG;
|
|
|
|
} else {
|
|
|
|
if (state->csc_mask & SS_BATDEAD) reg |= I365_CSC_BVD1;
|
|
|
|
if (state->csc_mask & SS_BATWARN) reg |= I365_CSC_BVD2;
|
|
|
|
if (state->csc_mask & SS_READY) reg |= I365_CSC_READY;
|
|
|
|
}
|
|
|
|
exca_writeb(socket, I365_CSCINT, reg);
|
|
|
|
exca_readb(socket, I365_CSC);
|
|
|
|
if(sock->zoom_video)
|
|
|
|
sock->zoom_video(sock, state->flags & SS_ZVCARD);
|
|
|
|
}
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge);
|
|
|
|
/* Socket event mask: get card insert/remove events.. */
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, CB_CDMASK);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_set_io_map(struct pcmcia_socket *sock, struct pccard_io_map *io)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
int map;
|
|
|
|
unsigned char ioctl, addr, enable;
|
|
|
|
|
|
|
|
map = io->map;
|
|
|
|
|
|
|
|
if (map > 1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
enable = I365_ENA_IO(map);
|
|
|
|
addr = exca_readb(socket, I365_ADDRWIN);
|
|
|
|
|
|
|
|
/* Disable the window before changing it.. */
|
|
|
|
if (addr & enable) {
|
|
|
|
addr &= ~enable;
|
|
|
|
exca_writeb(socket, I365_ADDRWIN, addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
exca_writew(socket, I365_IO(map)+I365_W_START, io->start);
|
|
|
|
exca_writew(socket, I365_IO(map)+I365_W_STOP, io->stop);
|
|
|
|
|
|
|
|
ioctl = exca_readb(socket, I365_IOCTL) & ~I365_IOCTL_MASK(map);
|
|
|
|
if (io->flags & MAP_0WS) ioctl |= I365_IOCTL_0WS(map);
|
|
|
|
if (io->flags & MAP_16BIT) ioctl |= I365_IOCTL_16BIT(map);
|
|
|
|
if (io->flags & MAP_AUTOSZ) ioctl |= I365_IOCTL_IOCS16(map);
|
|
|
|
exca_writeb(socket, I365_IOCTL, ioctl);
|
|
|
|
|
|
|
|
if (io->flags & MAP_ACTIVE)
|
|
|
|
exca_writeb(socket, I365_ADDRWIN, addr | enable);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_set_mem_map(struct pcmcia_socket *sock, struct pccard_mem_map *mem)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
struct pci_bus_region region;
|
|
|
|
int map;
|
|
|
|
unsigned char addr, enable;
|
|
|
|
unsigned int start, stop, card_start;
|
|
|
|
unsigned short word;
|
|
|
|
|
|
|
|
pcibios_resource_to_bus(socket->dev, ®ion, mem->res);
|
|
|
|
|
|
|
|
map = mem->map;
|
|
|
|
start = region.start;
|
|
|
|
stop = region.end;
|
|
|
|
card_start = mem->card_start;
|
|
|
|
|
|
|
|
if (map > 4 || start > stop || ((start ^ stop) >> 24) ||
|
|
|
|
(card_start >> 26) || mem->speed > 1000)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
enable = I365_ENA_MEM(map);
|
|
|
|
addr = exca_readb(socket, I365_ADDRWIN);
|
|
|
|
if (addr & enable) {
|
|
|
|
addr &= ~enable;
|
|
|
|
exca_writeb(socket, I365_ADDRWIN, addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
exca_writeb(socket, CB_MEM_PAGE(map), start >> 24);
|
|
|
|
|
|
|
|
word = (start >> 12) & 0x0fff;
|
|
|
|
if (mem->flags & MAP_16BIT)
|
|
|
|
word |= I365_MEM_16BIT;
|
|
|
|
if (mem->flags & MAP_0WS)
|
|
|
|
word |= I365_MEM_0WS;
|
|
|
|
exca_writew(socket, I365_MEM(map) + I365_W_START, word);
|
|
|
|
|
|
|
|
word = (stop >> 12) & 0x0fff;
|
|
|
|
switch (to_cycles(mem->speed)) {
|
|
|
|
case 0: break;
|
|
|
|
case 1: word |= I365_MEM_WS0; break;
|
|
|
|
case 2: word |= I365_MEM_WS1; break;
|
|
|
|
default: word |= I365_MEM_WS1 | I365_MEM_WS0; break;
|
|
|
|
}
|
|
|
|
exca_writew(socket, I365_MEM(map) + I365_W_STOP, word);
|
|
|
|
|
|
|
|
word = ((card_start - start) >> 12) & 0x3fff;
|
|
|
|
if (mem->flags & MAP_WRPROT)
|
|
|
|
word |= I365_MEM_WRPROT;
|
|
|
|
if (mem->flags & MAP_ATTRIB)
|
|
|
|
word |= I365_MEM_REG;
|
|
|
|
exca_writew(socket, I365_MEM(map) + I365_W_OFF, word);
|
|
|
|
|
|
|
|
if (mem->flags & MAP_ACTIVE)
|
|
|
|
exca_writeb(socket, I365_ADDRWIN, addr | enable);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-06-23 01:10:12 -06:00
|
|
|
|
|
|
|
static irqreturn_t yenta_interrupt(int irq, void *dev_id, struct pt_regs *regs)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
2005-06-23 01:10:12 -06:00
|
|
|
unsigned int events;
|
|
|
|
struct yenta_socket *socket = (struct yenta_socket *) dev_id;
|
2005-04-16 16:20:36 -06:00
|
|
|
u8 csc;
|
|
|
|
u32 cb_event;
|
|
|
|
|
|
|
|
/* Clear interrupt status for the event */
|
|
|
|
cb_event = cb_readl(socket, CB_SOCKET_EVENT);
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, cb_event);
|
|
|
|
|
|
|
|
csc = exca_readb(socket, I365_CSC);
|
|
|
|
|
|
|
|
events = (cb_event & (CB_CD1EVENT | CB_CD2EVENT)) ? SS_DETECT : 0 ;
|
|
|
|
events |= (csc & I365_CSC_DETECT) ? SS_DETECT : 0;
|
|
|
|
if (exca_readb(socket, I365_INTCTL) & I365_PC_IOCARD) {
|
|
|
|
events |= (csc & I365_CSC_STSCHG) ? SS_STSCHG : 0;
|
|
|
|
} else {
|
|
|
|
events |= (csc & I365_CSC_BVD1) ? SS_BATDEAD : 0;
|
|
|
|
events |= (csc & I365_CSC_BVD2) ? SS_BATWARN : 0;
|
|
|
|
events |= (csc & I365_CSC_READY) ? SS_READY : 0;
|
|
|
|
}
|
|
|
|
|
2005-06-23 01:10:12 -06:00
|
|
|
if (events)
|
2005-04-16 16:20:36 -06:00
|
|
|
pcmcia_parse_events(&socket->socket, events);
|
2005-06-23 01:10:12 -06:00
|
|
|
|
|
|
|
if (cb_event || csc)
|
2005-04-16 16:20:36 -06:00
|
|
|
return IRQ_HANDLED;
|
2005-06-23 01:10:12 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void yenta_interrupt_wrapper(unsigned long data)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = (struct yenta_socket *) data;
|
|
|
|
|
|
|
|
yenta_interrupt(0, (void *)socket, NULL);
|
|
|
|
socket->poll_timer.expires = jiffies + HZ;
|
|
|
|
add_timer(&socket->poll_timer);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void yenta_clear_maps(struct yenta_socket *socket)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
struct resource res = { .start = 0, .end = 0x0fff };
|
|
|
|
pccard_io_map io = { 0, 0, 0, 0, 1 };
|
|
|
|
pccard_mem_map mem = { .res = &res, };
|
|
|
|
|
|
|
|
yenta_set_socket(&socket->socket, &dead_socket);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
|
|
io.map = i;
|
|
|
|
yenta_set_io_map(&socket->socket, &io);
|
|
|
|
}
|
|
|
|
for (i = 0; i < 5; i++) {
|
|
|
|
mem.map = i;
|
|
|
|
yenta_set_mem_map(&socket->socket, &mem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-06-23 01:10:12 -06:00
|
|
|
/* redoes voltage interrogation if required */
|
|
|
|
static void yenta_interrogate(struct yenta_socket *socket)
|
|
|
|
{
|
|
|
|
u32 state;
|
|
|
|
|
|
|
|
state = cb_readl(socket, CB_SOCKET_STATE);
|
|
|
|
if (!(state & (CB_5VCARD | CB_3VCARD | CB_XVCARD | CB_YVCARD)) ||
|
|
|
|
(state & (CB_CDETECT1 | CB_CDETECT2 | CB_NOTACARD | CB_BADVCCREQ)) ||
|
|
|
|
((state & (CB_16BITCARD | CB_CBCARD)) == (CB_16BITCARD | CB_CBCARD)))
|
|
|
|
cb_writel(socket, CB_SOCKET_FORCE, CB_CVSTEST);
|
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/* Called at resume and initialization events */
|
|
|
|
static int yenta_sock_init(struct pcmcia_socket *sock)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
u16 bridge;
|
|
|
|
|
|
|
|
bridge = config_readw(socket, CB_BRIDGE_CONTROL) & ~CB_BRIDGE_INTR;
|
|
|
|
if (!socket->cb_irq)
|
|
|
|
bridge |= CB_BRIDGE_INTR;
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge);
|
|
|
|
|
|
|
|
exca_writeb(socket, I365_GBLCTL, 0x00);
|
|
|
|
exca_writeb(socket, I365_GENCTL, 0x00);
|
|
|
|
|
|
|
|
/* Redo card voltage interrogation */
|
2005-06-23 01:10:12 -06:00
|
|
|
yenta_interrogate(socket);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
yenta_clear_maps(socket);
|
|
|
|
|
|
|
|
if (socket->type && socket->type->sock_init)
|
|
|
|
socket->type->sock_init(socket);
|
|
|
|
|
|
|
|
/* Re-enable CSC interrupts */
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, CB_CDMASK);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int yenta_sock_suspend(struct pcmcia_socket *sock)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = container_of(sock, struct yenta_socket, socket);
|
|
|
|
|
|
|
|
/* Disable CSC interrupts */
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, 0x0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use an adaptive allocation for the memory resource,
|
|
|
|
* sometimes the memory behind pci bridges is limited:
|
|
|
|
* 1/8 of the size of the io window of the parent.
|
2005-07-12 14:58:17 -06:00
|
|
|
* max 4 MB, min 16 kB. We try very hard to not get below
|
|
|
|
* the "ACC" values, though.
|
2005-04-16 16:20:36 -06:00
|
|
|
*/
|
|
|
|
#define BRIDGE_MEM_MAX 4*1024*1024
|
2005-07-12 14:58:17 -06:00
|
|
|
#define BRIDGE_MEM_ACC 128*1024
|
2005-04-16 16:20:36 -06:00
|
|
|
#define BRIDGE_MEM_MIN 16*1024
|
|
|
|
|
2005-07-12 14:58:17 -06:00
|
|
|
#define BRIDGE_IO_MAX 512
|
|
|
|
#define BRIDGE_IO_ACC 256
|
2005-04-16 16:20:36 -06:00
|
|
|
#define BRIDGE_IO_MIN 32
|
|
|
|
|
|
|
|
#ifndef PCIBIOS_MIN_CARDBUS_IO
|
|
|
|
#define PCIBIOS_MIN_CARDBUS_IO PCIBIOS_MIN_IO
|
|
|
|
#endif
|
|
|
|
|
2005-07-12 14:58:17 -06:00
|
|
|
static int yenta_search_one_res(struct resource *root, struct resource *res,
|
|
|
|
u32 min)
|
|
|
|
{
|
|
|
|
u32 align, size, start, end;
|
|
|
|
|
|
|
|
if (res->flags & IORESOURCE_IO) {
|
|
|
|
align = 1024;
|
|
|
|
size = BRIDGE_IO_MAX;
|
|
|
|
start = PCIBIOS_MIN_CARDBUS_IO;
|
|
|
|
end = ~0U;
|
|
|
|
} else {
|
|
|
|
unsigned long avail = root->end - root->start;
|
|
|
|
int i;
|
|
|
|
size = BRIDGE_MEM_MAX;
|
|
|
|
if (size > avail/8) {
|
|
|
|
size=(avail+1)/8;
|
|
|
|
/* round size down to next power of 2 */
|
|
|
|
i = 0;
|
|
|
|
while ((size /= 2) != 0)
|
|
|
|
i++;
|
|
|
|
size = 1 << i;
|
|
|
|
}
|
|
|
|
if (size < min)
|
|
|
|
size = min;
|
|
|
|
align = size;
|
|
|
|
start = PCIBIOS_MIN_MEM;
|
|
|
|
end = ~0U;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (allocate_resource(root, res, size, start, end, align,
|
|
|
|
NULL, NULL)==0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
size = size/2;
|
|
|
|
align = size;
|
|
|
|
} while (size >= min);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int yenta_search_res(struct yenta_socket *socket, struct resource *res,
|
|
|
|
u32 min)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i=0; i<PCI_BUS_NUM_RESOURCES; i++) {
|
|
|
|
struct resource * root = socket->dev->bus->resource[i];
|
|
|
|
if (!root)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((res->flags ^ root->flags) &
|
|
|
|
(IORESOURCE_IO | IORESOURCE_MEM | IORESOURCE_PREFETCH))
|
|
|
|
continue; /* Wrong type */
|
|
|
|
|
|
|
|
if (yenta_search_one_res(root, res, min))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-07-12 14:58:16 -06:00
|
|
|
static void yenta_allocate_res(struct yenta_socket *socket, int nr, unsigned type, int addr_start, int addr_end)
|
2005-04-16 16:20:36 -06:00
|
|
|
{
|
|
|
|
struct resource *root, *res;
|
2005-08-04 19:06:21 -06:00
|
|
|
struct pci_bus_region region;
|
2005-04-16 16:20:36 -06:00
|
|
|
unsigned mask;
|
|
|
|
|
2005-06-27 17:28:02 -06:00
|
|
|
res = socket->dev->resource + PCI_BRIDGE_RESOURCES + nr;
|
|
|
|
/* Already allocated? */
|
|
|
|
if (res->parent)
|
2005-07-12 14:58:15 -06:00
|
|
|
return;
|
2005-06-27 17:28:02 -06:00
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
/* The granularity of the memory limit is 4kB, on IO it's 4 bytes */
|
|
|
|
mask = ~0xfff;
|
|
|
|
if (type & IORESOURCE_IO)
|
|
|
|
mask = ~3;
|
|
|
|
|
2005-08-04 19:06:21 -06:00
|
|
|
res->name = socket->dev->subordinate->name;
|
2005-04-16 16:20:36 -06:00
|
|
|
res->flags = type;
|
|
|
|
|
2005-08-04 19:06:21 -06:00
|
|
|
region.start = config_readl(socket, addr_start) & mask;
|
|
|
|
region.end = config_readl(socket, addr_end) | ~mask;
|
|
|
|
if (region.start && region.end > region.start && !override_bios) {
|
|
|
|
pcibios_bus_to_resource(socket->dev, res, ®ion);
|
2005-07-12 14:58:15 -06:00
|
|
|
root = pci_find_parent_resource(socket->dev, res);
|
|
|
|
if (root && (request_resource(root, res) == 0))
|
2005-04-16 16:20:36 -06:00
|
|
|
return;
|
2005-07-12 14:58:15 -06:00
|
|
|
printk(KERN_INFO "yenta %s: Preassigned resource %d busy or not available, reconfiguring...\n",
|
2005-04-16 16:20:36 -06:00
|
|
|
pci_name(socket->dev), nr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type & IORESOURCE_IO) {
|
2005-07-12 14:58:17 -06:00
|
|
|
if ((yenta_search_res(socket, res, BRIDGE_IO_MAX)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_IO_ACC)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_IO_MIN))) {
|
|
|
|
config_writel(socket, addr_start, res->start);
|
|
|
|
config_writel(socket, addr_end, res->end);
|
[PATCH] Obvious bugfix for yenta resource allocation
Recent changes (well, dating from 12 July) have broken cardbus on my
powerbook: I get 3 messages saying "no resource of type xxx available,
trying to continue", and if I plug in my wireless card, it complains
that there are no resources allocated to the card. This all worked in
2.6.12.
Looking at the code in yenta_socket.c, function yenta_allocate_res,
it's obvious what is wrong: if we get to line 639 (i.e. there wasn't a
usable preassigned resource), we will always flow through to line 668,
which is the printk that I was seeing, even if a resource was
successfully allocated. It looks to me as though there should be a
return statement after the two config_writel's in each of the 3
branches of the if statements, so that the function returns after
successfully setting up the resource.
The patch below adds these return statements, and with this patch,
cardbus works on my powerbook once again.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-08-02 05:51:36 -06:00
|
|
|
return;
|
2005-07-12 14:58:17 -06:00
|
|
|
}
|
2005-04-16 16:20:36 -06:00
|
|
|
} else {
|
2005-07-12 14:58:17 -06:00
|
|
|
if (type & IORESOURCE_PREFETCH) {
|
|
|
|
if ((yenta_search_res(socket, res, BRIDGE_MEM_MAX)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_MEM_ACC)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_MEM_MIN))) {
|
|
|
|
config_writel(socket, addr_start, res->start);
|
|
|
|
config_writel(socket, addr_end, res->end);
|
[PATCH] Obvious bugfix for yenta resource allocation
Recent changes (well, dating from 12 July) have broken cardbus on my
powerbook: I get 3 messages saying "no resource of type xxx available,
trying to continue", and if I plug in my wireless card, it complains
that there are no resources allocated to the card. This all worked in
2.6.12.
Looking at the code in yenta_socket.c, function yenta_allocate_res,
it's obvious what is wrong: if we get to line 639 (i.e. there wasn't a
usable preassigned resource), we will always flow through to line 668,
which is the printk that I was seeing, even if a resource was
successfully allocated. It looks to me as though there should be a
return statement after the two config_writel's in each of the 3
branches of the if statements, so that the function returns after
successfully setting up the resource.
The patch below adds these return statements, and with this patch,
cardbus works on my powerbook once again.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-08-02 05:51:36 -06:00
|
|
|
return;
|
2005-07-12 14:58:17 -06:00
|
|
|
}
|
|
|
|
/* Approximating prefetchable by non-prefetchable */
|
|
|
|
res->flags = IORESOURCE_MEM;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
2005-07-12 14:58:17 -06:00
|
|
|
if ((yenta_search_res(socket, res, BRIDGE_MEM_MAX)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_MEM_ACC)) ||
|
|
|
|
(yenta_search_res(socket, res, BRIDGE_MEM_MIN))) {
|
2005-07-12 14:58:16 -06:00
|
|
|
config_writel(socket, addr_start, res->start);
|
|
|
|
config_writel(socket, addr_end, res->end);
|
[PATCH] Obvious bugfix for yenta resource allocation
Recent changes (well, dating from 12 July) have broken cardbus on my
powerbook: I get 3 messages saying "no resource of type xxx available,
trying to continue", and if I plug in my wireless card, it complains
that there are no resources allocated to the card. This all worked in
2.6.12.
Looking at the code in yenta_socket.c, function yenta_allocate_res,
it's obvious what is wrong: if we get to line 639 (i.e. there wasn't a
usable preassigned resource), we will always flow through to line 668,
which is the printk that I was seeing, even if a resource was
successfully allocated. It looks to me as though there should be a
return statement after the two config_writel's in each of the 3
branches of the if statements, so that the function returns after
successfully setting up the resource.
The patch below adds these return statements, and with this patch,
cardbus works on my powerbook once again.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Dominik Brodowski <linux@dominikbrodowski.net>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-08-02 05:51:36 -06:00
|
|
|
return;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
2005-07-12 14:58:17 -06:00
|
|
|
}
|
|
|
|
|
2005-04-16 16:20:36 -06:00
|
|
|
printk(KERN_INFO "yenta %s: no resource of type %x available, trying to continue...\n",
|
2005-07-12 14:58:17 -06:00
|
|
|
pci_name(socket->dev), type);
|
|
|
|
res->start = res->end = res->flags = 0;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate the bridge mappings for the device..
|
|
|
|
*/
|
|
|
|
static void yenta_allocate_resources(struct yenta_socket *socket)
|
|
|
|
{
|
2005-07-12 14:58:16 -06:00
|
|
|
yenta_allocate_res(socket, 0, IORESOURCE_IO,
|
|
|
|
PCI_CB_IO_BASE_0, PCI_CB_IO_LIMIT_0);
|
|
|
|
yenta_allocate_res(socket, 1, IORESOURCE_IO,
|
|
|
|
PCI_CB_IO_BASE_1, PCI_CB_IO_LIMIT_1);
|
|
|
|
yenta_allocate_res(socket, 2, IORESOURCE_MEM|IORESOURCE_PREFETCH,
|
|
|
|
PCI_CB_MEMORY_BASE_0, PCI_CB_MEMORY_LIMIT_0);
|
|
|
|
yenta_allocate_res(socket, 3, IORESOURCE_MEM,
|
|
|
|
PCI_CB_MEMORY_BASE_1, PCI_CB_MEMORY_LIMIT_1);
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Free the bridge mappings for the device..
|
|
|
|
*/
|
|
|
|
static void yenta_free_resources(struct yenta_socket *socket)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i=0;i<4;i++) {
|
|
|
|
struct resource *res;
|
|
|
|
res = socket->dev->resource + PCI_BRIDGE_RESOURCES + i;
|
|
|
|
if (res->start != 0 && res->end != 0)
|
|
|
|
release_resource(res);
|
|
|
|
res->start = res->end = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close it down - release our resources and go home..
|
|
|
|
*/
|
|
|
|
static void yenta_close(struct pci_dev *dev)
|
|
|
|
{
|
|
|
|
struct yenta_socket *sock = pci_get_drvdata(dev);
|
|
|
|
|
|
|
|
/* we don't want a dying socket registered */
|
|
|
|
pcmcia_unregister_socket(&sock->socket);
|
|
|
|
|
|
|
|
/* Disable all events so we don't die in an IRQ storm */
|
|
|
|
cb_writel(sock, CB_SOCKET_MASK, 0x0);
|
|
|
|
exca_writeb(sock, I365_CSCINT, 0);
|
|
|
|
|
|
|
|
if (sock->cb_irq)
|
|
|
|
free_irq(sock->cb_irq, sock);
|
|
|
|
else
|
|
|
|
del_timer_sync(&sock->poll_timer);
|
|
|
|
|
|
|
|
if (sock->base)
|
|
|
|
iounmap(sock->base);
|
|
|
|
yenta_free_resources(sock);
|
|
|
|
|
|
|
|
pci_release_regions(dev);
|
|
|
|
pci_disable_device(dev);
|
|
|
|
pci_set_drvdata(dev, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct pccard_operations yenta_socket_operations = {
|
|
|
|
.init = yenta_sock_init,
|
|
|
|
.suspend = yenta_sock_suspend,
|
|
|
|
.get_status = yenta_get_status,
|
|
|
|
.get_socket = yenta_get_socket,
|
|
|
|
.set_socket = yenta_set_socket,
|
|
|
|
.set_io_map = yenta_set_io_map,
|
|
|
|
.set_mem_map = yenta_set_mem_map,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#include "ti113x.h"
|
|
|
|
#include "ricoh.h"
|
|
|
|
#include "topic.h"
|
|
|
|
#include "o2micro.h"
|
|
|
|
|
|
|
|
enum {
|
|
|
|
CARDBUS_TYPE_DEFAULT = -1,
|
|
|
|
CARDBUS_TYPE_TI,
|
|
|
|
CARDBUS_TYPE_TI113X,
|
|
|
|
CARDBUS_TYPE_TI12XX,
|
|
|
|
CARDBUS_TYPE_TI1250,
|
|
|
|
CARDBUS_TYPE_RICOH,
|
|
|
|
CARDBUS_TYPE_TOPIC97,
|
|
|
|
CARDBUS_TYPE_O2MICRO,
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Different cardbus controllers have slightly different
|
|
|
|
* initialization sequences etc details. List them here..
|
|
|
|
*/
|
|
|
|
static struct cardbus_type cardbus_type[] = {
|
|
|
|
[CARDBUS_TYPE_TI] = {
|
|
|
|
.override = ti_override,
|
|
|
|
.save_state = ti_save_state,
|
|
|
|
.restore_state = ti_restore_state,
|
|
|
|
.sock_init = ti_init,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_TI113X] = {
|
|
|
|
.override = ti113x_override,
|
|
|
|
.save_state = ti_save_state,
|
|
|
|
.restore_state = ti_restore_state,
|
|
|
|
.sock_init = ti_init,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_TI12XX] = {
|
|
|
|
.override = ti12xx_override,
|
|
|
|
.save_state = ti_save_state,
|
|
|
|
.restore_state = ti_restore_state,
|
|
|
|
.sock_init = ti_init,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_TI1250] = {
|
|
|
|
.override = ti1250_override,
|
|
|
|
.save_state = ti_save_state,
|
|
|
|
.restore_state = ti_restore_state,
|
|
|
|
.sock_init = ti_init,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_RICOH] = {
|
|
|
|
.override = ricoh_override,
|
|
|
|
.save_state = ricoh_save_state,
|
|
|
|
.restore_state = ricoh_restore_state,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_TOPIC97] = {
|
|
|
|
.override = topic97_override,
|
|
|
|
},
|
|
|
|
[CARDBUS_TYPE_O2MICRO] = {
|
|
|
|
.override = o2micro_override,
|
|
|
|
.restore_state = o2micro_restore_state,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only probe "regular" interrupts, don't
|
|
|
|
* touch dangerous spots like the mouse irq,
|
|
|
|
* because there are mice that apparently
|
|
|
|
* get really confused if they get fondled
|
|
|
|
* too intimately.
|
|
|
|
*
|
|
|
|
* Default to 11, 10, 9, 7, 6, 5, 4, 3.
|
|
|
|
*/
|
|
|
|
static u32 isa_interrupts = 0x0ef8;
|
|
|
|
|
|
|
|
static unsigned int yenta_probe_irq(struct yenta_socket *socket, u32 isa_irq_mask)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned long val;
|
|
|
|
u16 bridge_ctrl;
|
|
|
|
u32 mask;
|
|
|
|
|
|
|
|
/* Set up ISA irq routing to probe the ISA irqs.. */
|
|
|
|
bridge_ctrl = config_readw(socket, CB_BRIDGE_CONTROL);
|
|
|
|
if (!(bridge_ctrl & CB_BRIDGE_INTR)) {
|
|
|
|
bridge_ctrl |= CB_BRIDGE_INTR;
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Probe for usable interrupts using the force
|
|
|
|
* register to generate bogus card status events.
|
|
|
|
*/
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, CB_CSTSMASK);
|
|
|
|
exca_writeb(socket, I365_CSCINT, 0);
|
|
|
|
val = probe_irq_on() & isa_irq_mask;
|
|
|
|
for (i = 1; i < 16; i++) {
|
|
|
|
if (!((val >> i) & 1))
|
|
|
|
continue;
|
|
|
|
exca_writeb(socket, I365_CSCINT, I365_CSC_STSCHG | (i << 4));
|
|
|
|
cb_writel(socket, CB_SOCKET_FORCE, CB_FCARDSTS);
|
|
|
|
udelay(100);
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
}
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, 0);
|
|
|
|
exca_writeb(socket, I365_CSCINT, 0);
|
|
|
|
|
|
|
|
mask = probe_irq_mask(val) & 0xffff;
|
|
|
|
|
|
|
|
bridge_ctrl &= ~CB_BRIDGE_INTR;
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl);
|
|
|
|
|
|
|
|
return mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* interrupt handler, only used during probing */
|
|
|
|
static irqreturn_t yenta_probe_handler(int irq, void *dev_id, struct pt_regs *regs)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = (struct yenta_socket *) dev_id;
|
|
|
|
u8 csc;
|
|
|
|
u32 cb_event;
|
|
|
|
|
|
|
|
/* Clear interrupt status for the event */
|
|
|
|
cb_event = cb_readl(socket, CB_SOCKET_EVENT);
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
csc = exca_readb(socket, I365_CSC);
|
|
|
|
|
|
|
|
if (cb_event || csc) {
|
|
|
|
socket->probe_status = 1;
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* probes the PCI interrupt, use only on override functions */
|
|
|
|
static int yenta_probe_cb_irq(struct yenta_socket *socket)
|
|
|
|
{
|
|
|
|
u16 bridge_ctrl;
|
|
|
|
|
|
|
|
if (!socket->cb_irq)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
socket->probe_status = 0;
|
|
|
|
|
|
|
|
/* disable ISA interrupts */
|
|
|
|
bridge_ctrl = config_readw(socket, CB_BRIDGE_CONTROL);
|
|
|
|
bridge_ctrl &= ~CB_BRIDGE_INTR;
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge_ctrl);
|
|
|
|
|
|
|
|
if (request_irq(socket->cb_irq, yenta_probe_handler, SA_SHIRQ, "yenta", socket)) {
|
|
|
|
printk(KERN_WARNING "Yenta: request_irq() in yenta_probe_cb_irq() failed!\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* generate interrupt, wait */
|
|
|
|
exca_writeb(socket, I365_CSCINT, I365_CSC_STSCHG);
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, CB_CSTSMASK);
|
|
|
|
cb_writel(socket, CB_SOCKET_FORCE, CB_FCARDSTS);
|
|
|
|
|
|
|
|
msleep(100);
|
|
|
|
|
|
|
|
/* disable interrupts */
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, 0);
|
|
|
|
exca_writeb(socket, I365_CSCINT, 0);
|
|
|
|
cb_writel(socket, CB_SOCKET_EVENT, -1);
|
|
|
|
exca_readb(socket, I365_CSC);
|
|
|
|
|
|
|
|
free_irq(socket->cb_irq, socket);
|
|
|
|
|
|
|
|
return (int) socket->probe_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set static data that doesn't need re-initializing..
|
|
|
|
*/
|
|
|
|
static void yenta_get_socket_capabilities(struct yenta_socket *socket, u32 isa_irq_mask)
|
|
|
|
{
|
|
|
|
socket->socket.pci_irq = socket->cb_irq;
|
2005-06-23 01:10:12 -06:00
|
|
|
if (isa_probe)
|
|
|
|
socket->socket.irq_mask = yenta_probe_irq(socket, isa_irq_mask);
|
|
|
|
else
|
|
|
|
socket->socket.irq_mask = 0;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
printk(KERN_INFO "Yenta: ISA IRQ mask 0x%04x, PCI irq %d\n",
|
|
|
|
socket->socket.irq_mask, socket->cb_irq);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize the standard cardbus registers
|
|
|
|
*/
|
|
|
|
static void yenta_config_init(struct yenta_socket *socket)
|
|
|
|
{
|
|
|
|
u16 bridge;
|
|
|
|
struct pci_dev *dev = socket->dev;
|
|
|
|
|
|
|
|
pci_set_power_state(socket->dev, 0);
|
|
|
|
|
|
|
|
config_writel(socket, CB_LEGACY_MODE_BASE, 0);
|
|
|
|
config_writel(socket, PCI_BASE_ADDRESS_0, dev->resource[0].start);
|
|
|
|
config_writew(socket, PCI_COMMAND,
|
|
|
|
PCI_COMMAND_IO |
|
|
|
|
PCI_COMMAND_MEMORY |
|
|
|
|
PCI_COMMAND_MASTER |
|
|
|
|
PCI_COMMAND_WAIT);
|
|
|
|
|
|
|
|
/* MAGIC NUMBERS! Fixme */
|
|
|
|
config_writeb(socket, PCI_CACHE_LINE_SIZE, L1_CACHE_BYTES / 4);
|
|
|
|
config_writeb(socket, PCI_LATENCY_TIMER, 168);
|
|
|
|
config_writel(socket, PCI_PRIMARY_BUS,
|
|
|
|
(176 << 24) | /* sec. latency timer */
|
|
|
|
(dev->subordinate->subordinate << 16) | /* subordinate bus */
|
|
|
|
(dev->subordinate->secondary << 8) | /* secondary bus */
|
|
|
|
dev->subordinate->primary); /* primary bus */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the bridging state:
|
|
|
|
* - enable write posting.
|
|
|
|
* - memory window 0 prefetchable, window 1 non-prefetchable
|
|
|
|
* - PCI interrupts enabled if a PCI interrupt exists..
|
|
|
|
*/
|
|
|
|
bridge = config_readw(socket, CB_BRIDGE_CONTROL);
|
|
|
|
bridge &= ~(CB_BRIDGE_CRST | CB_BRIDGE_PREFETCH1 | CB_BRIDGE_INTR | CB_BRIDGE_ISAEN | CB_BRIDGE_VGAEN);
|
|
|
|
bridge |= CB_BRIDGE_PREFETCH0 | CB_BRIDGE_POSTEN | CB_BRIDGE_INTR;
|
|
|
|
config_writew(socket, CB_BRIDGE_CONTROL, bridge);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize a cardbus controller. Make sure we have a usable
|
|
|
|
* interrupt, and that we can map the cardbus area. Fill in the
|
|
|
|
* socket information structure..
|
|
|
|
*/
|
|
|
|
static int __devinit yenta_probe (struct pci_dev *dev, const struct pci_device_id *id)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
socket = kmalloc(sizeof(struct yenta_socket), GFP_KERNEL);
|
|
|
|
if (!socket)
|
|
|
|
return -ENOMEM;
|
|
|
|
memset(socket, 0, sizeof(*socket));
|
|
|
|
|
|
|
|
/* prepare pcmcia_socket */
|
|
|
|
socket->socket.ops = ¥ta_socket_operations;
|
|
|
|
socket->socket.resource_ops = &pccard_nonstatic_ops;
|
|
|
|
socket->socket.dev.dev = &dev->dev;
|
|
|
|
socket->socket.driver_data = socket;
|
|
|
|
socket->socket.owner = THIS_MODULE;
|
2005-07-07 18:59:07 -06:00
|
|
|
socket->socket.features = SS_CAP_PAGE_REGS | SS_CAP_PCCARD;
|
|
|
|
socket->socket.map_size = 0x1000;
|
|
|
|
socket->socket.cb_dev = dev;
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/* prepare struct yenta_socket */
|
|
|
|
socket->dev = dev;
|
|
|
|
pci_set_drvdata(dev, socket);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do some basic sanity checking..
|
|
|
|
*/
|
|
|
|
if (pci_enable_device(dev)) {
|
|
|
|
ret = -EBUSY;
|
|
|
|
goto free;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pci_request_regions(dev, "yenta_socket");
|
|
|
|
if (ret)
|
|
|
|
goto disable;
|
|
|
|
|
|
|
|
if (!pci_resource_start(dev, 0)) {
|
|
|
|
printk(KERN_ERR "No cardbus resource!\n");
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Ok, start setup.. Map the cardbus registers,
|
|
|
|
* and request the IRQ.
|
|
|
|
*/
|
|
|
|
socket->base = ioremap(pci_resource_start(dev, 0), 0x1000);
|
|
|
|
if (!socket->base) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto release;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* report the subsystem vendor and device for help debugging
|
|
|
|
* the irq stuff...
|
|
|
|
*/
|
|
|
|
printk(KERN_INFO "Yenta: CardBus bridge found at %s [%04x:%04x]\n",
|
|
|
|
pci_name(dev), dev->subsystem_vendor, dev->subsystem_device);
|
|
|
|
|
|
|
|
yenta_config_init(socket);
|
|
|
|
|
|
|
|
/* Disable all events */
|
|
|
|
cb_writel(socket, CB_SOCKET_MASK, 0x0);
|
|
|
|
|
|
|
|
/* Set up the bridge regions.. */
|
|
|
|
yenta_allocate_resources(socket);
|
|
|
|
|
|
|
|
socket->cb_irq = dev->irq;
|
|
|
|
|
|
|
|
/* Do we have special options for the device? */
|
|
|
|
if (id->driver_data != CARDBUS_TYPE_DEFAULT &&
|
|
|
|
id->driver_data < ARRAY_SIZE(cardbus_type)) {
|
|
|
|
socket->type = &cardbus_type[id->driver_data];
|
|
|
|
|
|
|
|
ret = socket->type->override(socket);
|
|
|
|
if (ret < 0)
|
|
|
|
goto unmap;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We must finish initialization here */
|
|
|
|
|
|
|
|
if (!socket->cb_irq || request_irq(socket->cb_irq, yenta_interrupt, SA_SHIRQ, "yenta", socket)) {
|
|
|
|
/* No IRQ or request_irq failed. Poll */
|
|
|
|
socket->cb_irq = 0; /* But zero is a valid IRQ number. */
|
|
|
|
init_timer(&socket->poll_timer);
|
|
|
|
socket->poll_timer.function = yenta_interrupt_wrapper;
|
|
|
|
socket->poll_timer.data = (unsigned long)socket;
|
|
|
|
socket->poll_timer.expires = jiffies + HZ;
|
|
|
|
add_timer(&socket->poll_timer);
|
2005-07-07 18:59:07 -06:00
|
|
|
printk(KERN_INFO "Yenta: no PCI IRQ, CardBus support disabled for this socket.\n"
|
|
|
|
KERN_INFO "Yenta: check your BIOS CardBus, BIOS IRQ or ACPI settings.\n");
|
|
|
|
} else {
|
|
|
|
socket->socket.features |= SS_CAP_CARDBUS;
|
2005-04-16 16:20:36 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Figure out what the dang thing can do for the PCMCIA layer... */
|
2005-06-23 01:10:12 -06:00
|
|
|
yenta_interrogate(socket);
|
2005-04-16 16:20:36 -06:00
|
|
|
yenta_get_socket_capabilities(socket, isa_interrupts);
|
|
|
|
printk(KERN_INFO "Socket status: %08x\n", cb_readl(socket, CB_SOCKET_STATE));
|
|
|
|
|
|
|
|
/* Register it with the pcmcia layer.. */
|
|
|
|
ret = pcmcia_register_socket(&socket->socket);
|
|
|
|
if (ret == 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
unmap:
|
|
|
|
iounmap(socket->base);
|
|
|
|
release:
|
|
|
|
pci_release_regions(dev);
|
|
|
|
disable:
|
|
|
|
pci_disable_device(dev);
|
|
|
|
free:
|
|
|
|
kfree(socket);
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int yenta_dev_suspend (struct pci_dev *dev, pm_message_t state)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = pci_get_drvdata(dev);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = pcmcia_socket_dev_suspend(&dev->dev, state);
|
|
|
|
|
|
|
|
if (socket) {
|
|
|
|
if (socket->type && socket->type->save_state)
|
|
|
|
socket->type->save_state(socket);
|
|
|
|
|
|
|
|
/* FIXME: pci_save_state needs to have a better interface */
|
|
|
|
pci_save_state(dev);
|
|
|
|
pci_read_config_dword(dev, 16*4, &socket->saved_state[0]);
|
|
|
|
pci_read_config_dword(dev, 17*4, &socket->saved_state[1]);
|
2005-03-18 14:43:54 -07:00
|
|
|
pci_disable_device(dev);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Some laptops (IBM T22) do not like us putting the Cardbus
|
|
|
|
* bridge into D3. At a guess, some other laptop will
|
|
|
|
* probably require this, so leave it commented out for now.
|
|
|
|
*/
|
|
|
|
/* pci_set_power_state(dev, 3); */
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int yenta_dev_resume (struct pci_dev *dev)
|
|
|
|
{
|
|
|
|
struct yenta_socket *socket = pci_get_drvdata(dev);
|
|
|
|
|
|
|
|
if (socket) {
|
|
|
|
pci_set_power_state(dev, 0);
|
|
|
|
/* FIXME: pci_restore_state needs to have a better interface */
|
|
|
|
pci_restore_state(dev);
|
|
|
|
pci_write_config_dword(dev, 16*4, socket->saved_state[0]);
|
|
|
|
pci_write_config_dword(dev, 17*4, socket->saved_state[1]);
|
2005-03-18 14:43:54 -07:00
|
|
|
pci_enable_device(dev);
|
|
|
|
pci_set_master(dev);
|
2005-04-16 16:20:36 -06:00
|
|
|
|
|
|
|
if (socket->type && socket->type->restore_state)
|
|
|
|
socket->type->restore_state(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
return pcmcia_socket_dev_resume(&dev->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define CB_ID(vend,dev,type) \
|
|
|
|
{ \
|
|
|
|
.vendor = vend, \
|
|
|
|
.device = dev, \
|
|
|
|
.subvendor = PCI_ANY_ID, \
|
|
|
|
.subdevice = PCI_ANY_ID, \
|
|
|
|
.class = PCI_CLASS_BRIDGE_CARDBUS << 8, \
|
|
|
|
.class_mask = ~0, \
|
|
|
|
.driver_data = CARDBUS_TYPE_##type, \
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pci_device_id yenta_table [] = {
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1031, TI),
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TBD: Check if these TI variants can use more
|
|
|
|
* advanced overrides instead. (I can't get the
|
|
|
|
* data sheets for these devices. --rmk)
|
|
|
|
*/
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1210, TI),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1130, TI113X),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1131, TI113X),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1211, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1220, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1221, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1225, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251A, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1251B, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1420, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1450, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1451A, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1510, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1520, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1620, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4410, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4450, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4451, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4510, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_4520, TI12XX),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1250, TI1250),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1410, TI1250),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1211, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1225, TI12XX),
|
|
|
|
CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1410, TI1250),
|
|
|
|
CB_ID(PCI_VENDOR_ID_ENE, PCI_DEVICE_ID_ENE_1420, TI12XX),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C465, RICOH),
|
|
|
|
CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C466, RICOH),
|
|
|
|
CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C475, RICOH),
|
|
|
|
CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C476, RICOH),
|
|
|
|
CB_ID(PCI_VENDOR_ID_RICOH, PCI_DEVICE_ID_RICOH_RL5C478, RICOH),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC97, TOPIC97),
|
|
|
|
CB_ID(PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_TOSHIBA_TOPIC100, TOPIC97),
|
|
|
|
|
|
|
|
CB_ID(PCI_VENDOR_ID_O2, PCI_ANY_ID, O2MICRO),
|
|
|
|
|
|
|
|
/* match any cardbus bridge */
|
|
|
|
CB_ID(PCI_ANY_ID, PCI_ANY_ID, DEFAULT),
|
|
|
|
{ /* all zeroes */ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, yenta_table);
|
|
|
|
|
|
|
|
|
|
|
|
static struct pci_driver yenta_cardbus_driver = {
|
|
|
|
.name = "yenta_cardbus",
|
|
|
|
.id_table = yenta_table,
|
|
|
|
.probe = yenta_probe,
|
|
|
|
.remove = __devexit_p(yenta_close),
|
|
|
|
.suspend = yenta_dev_suspend,
|
|
|
|
.resume = yenta_dev_resume,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static int __init yenta_socket_init(void)
|
|
|
|
{
|
|
|
|
return pci_register_driver (¥ta_cardbus_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __exit yenta_socket_exit (void)
|
|
|
|
{
|
|
|
|
pci_unregister_driver (¥ta_cardbus_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module_init(yenta_socket_init);
|
|
|
|
module_exit(yenta_socket_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|