2005-06-28 06:28:53 +07:00
|
|
|
/*
|
|
|
|
* PCMCIA 16-bit resource management functions
|
|
|
|
*
|
|
|
|
* The initial developer of the original code is David A. Hinds
|
|
|
|
* <dahinds@users.sourceforge.net>. Portions created by David A. Hinds
|
|
|
|
* are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Copyright (C) 1999 David A. Hinds
|
2010-07-31 12:09:32 +07:00
|
|
|
* Copyright (C) 2004-2010 Dominik Brodowski
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
|
|
* published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/pci.h>
|
|
|
|
#include <linux/device.h>
|
2009-10-19 04:32:33 +07:00
|
|
|
#include <linux/netdevice.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-04-09 01:33:16 +07:00
|
|
|
#include <asm/irq.h>
|
|
|
|
|
2005-06-28 06:28:53 +07:00
|
|
|
#include <pcmcia/ss.h>
|
|
|
|
#include <pcmcia/cistpl.h>
|
|
|
|
#include <pcmcia/cisreg.h>
|
|
|
|
#include <pcmcia/ds.h>
|
|
|
|
|
|
|
|
#include "cs_internal.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Access speed for IO windows */
|
2009-12-08 04:11:45 +07:00
|
|
|
static int io_speed;
|
2005-06-28 06:28:53 +07:00
|
|
|
module_param(io_speed, int, 0444);
|
|
|
|
|
|
|
|
|
2010-01-06 20:03:03 +07:00
|
|
|
int pcmcia_validate_mem(struct pcmcia_socket *s)
|
|
|
|
{
|
|
|
|
if (s->resource_ops->validate_mem)
|
|
|
|
return s->resource_ops->validate_mem(s);
|
|
|
|
/* if there is no callback, we can assume that everything is OK */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct resource *pcmcia_find_mem_region(u_long base, u_long num, u_long align,
|
|
|
|
int low, struct pcmcia_socket *s)
|
|
|
|
{
|
|
|
|
if (s->resource_ops->find_mem)
|
|
|
|
return s->resource_ops->find_mem(base, num, align, low, s);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
/**
|
|
|
|
* release_io_space() - release IO ports allocated with alloc_io_space()
|
|
|
|
* @s: pcmcia socket
|
|
|
|
* @res: resource to release
|
|
|
|
*
|
|
|
|
*/
|
2010-07-25 18:10:22 +07:00
|
|
|
static void release_io_space(struct pcmcia_socket *s, struct resource *res)
|
|
|
|
{
|
|
|
|
resource_size_t num = resource_size(res);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
dev_dbg(&s->dev, "release_io_space for %pR\n", res);
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
|
|
|
if (!s->io[i].res)
|
|
|
|
continue;
|
|
|
|
if ((s->io[i].res->start <= res->start) &&
|
|
|
|
(s->io[i].res->end >= res->end)) {
|
|
|
|
s->io[i].InUse -= num;
|
|
|
|
if (res->parent)
|
|
|
|
release_resource(res);
|
|
|
|
res->start = res->end = 0;
|
|
|
|
res->flags = IORESOURCE_IO;
|
|
|
|
/* Free the window if no one else is using it */
|
|
|
|
if (s->io[i].InUse == 0) {
|
|
|
|
release_resource(s->io[i].res);
|
|
|
|
kfree(s->io[i].res);
|
|
|
|
s->io[i].res = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-31 12:09:32 +07:00
|
|
|
}
|
|
|
|
|
2010-07-25 18:10:22 +07:00
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
/**
|
|
|
|
* alloc_io_space() - allocate IO ports for use by a PCMCIA device
|
|
|
|
* @s: pcmcia socket
|
|
|
|
* @res: resource to allocate (begin: begin, end: size)
|
|
|
|
* @lines: number of IO lines decoded by the PCMCIA card
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
|
|
|
* Special stuff for managing IO windows, because they are scarce
|
|
|
|
*/
|
2010-07-24 18:14:44 +07:00
|
|
|
static int alloc_io_space(struct pcmcia_socket *s, struct resource *res,
|
|
|
|
unsigned int lines)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2010-03-20 19:10:47 +07:00
|
|
|
unsigned int align;
|
2010-07-24 18:14:44 +07:00
|
|
|
unsigned int base = res->start;
|
|
|
|
unsigned int num = res->end;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
res->flags |= IORESOURCE_IO;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-24 22:23:51 +07:00
|
|
|
dev_dbg(&s->dev, "alloc_io_space request for %pR, %d lines\n",
|
|
|
|
res, lines);
|
2010-07-24 18:14:44 +07:00
|
|
|
|
|
|
|
align = base ? (lines ? 1<<lines : 0) : 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
if (align && (align < num)) {
|
2010-07-24 18:14:44 +07:00
|
|
|
if (base) {
|
|
|
|
dev_dbg(&s->dev, "odd IO request\n");
|
2005-06-28 06:28:53 +07:00
|
|
|
align = 0;
|
|
|
|
} else
|
2009-12-08 04:11:45 +07:00
|
|
|
while (align && (align < num))
|
|
|
|
align <<= 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2010-07-24 18:14:44 +07:00
|
|
|
if (base & ~(align-1)) {
|
|
|
|
dev_dbg(&s->dev, "odd IO request\n");
|
2005-06-28 06:28:53 +07:00
|
|
|
align = 0;
|
|
|
|
}
|
2010-03-20 19:10:47 +07:00
|
|
|
|
2010-07-25 18:10:22 +07:00
|
|
|
ret = s->resource_ops->find_io(s, res->flags, &base, num, align,
|
|
|
|
&res->parent);
|
2010-07-24 18:14:44 +07:00
|
|
|
if (ret) {
|
2010-07-25 18:10:22 +07:00
|
|
|
dev_dbg(&s->dev, "alloc_io_space request failed (%d)\n", ret);
|
2010-07-24 18:14:44 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
res->start = base;
|
|
|
|
res->end = res->start + num - 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-25 18:10:22 +07:00
|
|
|
if (res->parent) {
|
|
|
|
ret = request_resource(res->parent, res);
|
|
|
|
if (ret) {
|
|
|
|
dev_warn(&s->dev,
|
|
|
|
"request_resource %pR failed: %d\n", res, ret);
|
|
|
|
res->parent = NULL;
|
|
|
|
release_io_space(s, res);
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
|
|
|
}
|
2010-07-25 18:10:22 +07:00
|
|
|
dev_dbg(&s->dev, "alloc_io_space request result %d: %pR\n", ret, res);
|
|
|
|
return ret;
|
2010-07-31 12:09:32 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
|
2010-07-24 17:23:21 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_access_config() - read or write card configuration registers
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
2010-07-24 17:23:21 +07:00
|
|
|
* pcmcia_access_config() reads and writes configuration registers in
|
|
|
|
* attribute memory. Memory window 0 is reserved for this and the tuple
|
|
|
|
* reading services. Drivers must use pcmcia_read_config_byte() or
|
|
|
|
* pcmcia_write_config_byte().
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-07-24 17:23:21 +07:00
|
|
|
static int pcmcia_access_config(struct pcmcia_device *p_dev,
|
|
|
|
off_t where, u8 *val,
|
|
|
|
int (*accessf) (struct pcmcia_socket *s,
|
|
|
|
int attr, unsigned int addr,
|
|
|
|
unsigned int len, void *ptr))
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2006-01-11 02:48:59 +07:00
|
|
|
struct pcmcia_socket *s;
|
2005-06-28 06:28:53 +07:00
|
|
|
config_t *c;
|
|
|
|
int addr;
|
2010-03-30 23:07:50 +07:00
|
|
|
int ret = 0;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2006-01-11 02:48:59 +07:00
|
|
|
s = p_dev->socket;
|
2010-01-18 00:31:34 +07:00
|
|
|
|
|
|
|
mutex_lock(&s->ops_mutex);
|
2006-01-11 02:48:59 +07:00
|
|
|
c = p_dev->function_config;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2009-10-24 17:20:18 +07:00
|
|
|
if (!(c->state & CONFIG_LOCKED)) {
|
2011-04-27 13:28:26 +07:00
|
|
|
dev_dbg(&p_dev->dev, "Configuration isn't locked\n");
|
2010-01-18 00:31:34 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 16:23:42 +07:00
|
|
|
return -EACCES;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-29 23:35:47 +07:00
|
|
|
addr = (p_dev->config_base + where) >> 1;
|
2010-07-24 17:23:21 +07:00
|
|
|
|
|
|
|
ret = accessf(s, 1, addr, 1, val);
|
|
|
|
|
2010-03-30 23:07:50 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2010-07-24 17:23:21 +07:00
|
|
|
|
2010-03-30 23:07:50 +07:00
|
|
|
return ret;
|
2010-07-31 12:09:32 +07:00
|
|
|
}
|
2010-07-24 17:23:21 +07:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_read_config_byte() - read a byte from a card configuration register
|
|
|
|
*
|
|
|
|
* pcmcia_read_config_byte() reads a byte from a configuration register in
|
|
|
|
* attribute memory.
|
|
|
|
*/
|
|
|
|
int pcmcia_read_config_byte(struct pcmcia_device *p_dev, off_t where, u8 *val)
|
|
|
|
{
|
|
|
|
return pcmcia_access_config(p_dev, where, val, pcmcia_read_cis_mem);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_read_config_byte);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_write_config_byte() - write a byte to a card configuration register
|
|
|
|
*
|
|
|
|
* pcmcia_write_config_byte() writes a byte to a configuration register in
|
|
|
|
* attribute memory.
|
|
|
|
*/
|
|
|
|
int pcmcia_write_config_byte(struct pcmcia_device *p_dev, off_t where, u8 val)
|
|
|
|
{
|
|
|
|
return pcmcia_access_config(p_dev, where, &val, pcmcia_write_cis_mem);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_write_config_byte);
|
2005-06-28 06:28:54 +07:00
|
|
|
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_map_mem_page() - modify iomem window to point to a different offset
|
|
|
|
* @p_dev: pcmcia device
|
|
|
|
* @res: iomem resource already enabled by pcmcia_request_window()
|
|
|
|
* @offset: card_offset to map
|
|
|
|
*
|
|
|
|
* pcmcia_map_mem_page() modifies what can be read and written by accessing
|
|
|
|
* an iomem range previously enabled by pcmcia_request_window(), by setting
|
|
|
|
* the card_offset value to @offset.
|
|
|
|
*/
|
2010-07-28 15:59:06 +07:00
|
|
|
int pcmcia_map_mem_page(struct pcmcia_device *p_dev, struct resource *res,
|
2010-07-24 23:46:42 +07:00
|
|
|
unsigned int offset)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2006-12-13 17:46:53 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2010-07-25 00:03:02 +07:00
|
|
|
unsigned int w;
|
2010-01-13 03:42:51 +07:00
|
|
|
int ret;
|
2006-12-13 17:46:43 +07:00
|
|
|
|
2010-07-25 00:03:02 +07:00
|
|
|
w = ((res->flags & IORESOURCE_BITS & WIN_FLAGS_REQ) >> 2) - 1;
|
|
|
|
if (w >= MAX_WIN)
|
2008-08-03 16:18:00 +07:00
|
|
|
return -EINVAL;
|
2010-07-24 23:46:42 +07:00
|
|
|
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_lock(&s->ops_mutex);
|
2010-07-25 00:03:02 +07:00
|
|
|
s->win[w].card_start = offset;
|
|
|
|
ret = s->ops->set_mem_map(s, &s->win[w]);
|
2010-01-13 03:42:51 +07:00
|
|
|
if (ret)
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_warn(&p_dev->dev, "failed to set_mem_map\n");
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
return ret;
|
2010-07-31 12:09:32 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
EXPORT_SYMBOL(pcmcia_map_mem_page);
|
|
|
|
|
|
|
|
|
2010-07-29 19:06:42 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_fixup_iowidth() - reduce io width to 8bit
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: pcmcia device
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
2010-07-29 19:06:42 +07:00
|
|
|
* pcmcia_fixup_iowidth() allows a PCMCIA device driver to reduce the
|
2010-07-30 00:27:09 +07:00
|
|
|
* IO width to 8bit after having called pcmcia_enable_device()
|
2010-07-29 19:06:42 +07:00
|
|
|
* previously.
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-07-29 19:06:42 +07:00
|
|
|
int pcmcia_fixup_iowidth(struct pcmcia_device *p_dev)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2010-07-29 19:06:42 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
|
|
|
pccard_io_map io_off = { 0, 0, 0, 0, 1 };
|
|
|
|
pccard_io_map io_on;
|
|
|
|
int i, ret = 0;
|
2010-01-18 00:31:34 +07:00
|
|
|
|
|
|
|
mutex_lock(&s->ops_mutex);
|
2006-01-11 02:41:27 +07:00
|
|
|
|
2010-07-29 19:06:42 +07:00
|
|
|
dev_dbg(&p_dev->dev, "fixup iowidth to 8bit\n");
|
|
|
|
|
|
|
|
if (!(s->state & SOCKET_PRESENT) ||
|
|
|
|
!(p_dev->function_config->state & CONFIG_LOCKED)) {
|
|
|
|
dev_dbg(&p_dev->dev, "No card? Config not locked?\n");
|
2010-03-16 21:57:44 +07:00
|
|
|
ret = -EACCES;
|
|
|
|
goto unlock;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-29 19:06:42 +07:00
|
|
|
io_on.speed = io_speed;
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
|
|
|
if (!s->io[i].res)
|
|
|
|
continue;
|
|
|
|
io_off.map = i;
|
|
|
|
io_on.map = i;
|
|
|
|
|
|
|
|
io_on.flags = MAP_ACTIVE | IO_DATA_PATH_WIDTH_8;
|
|
|
|
io_on.start = s->io[i].res->start;
|
|
|
|
io_on.stop = s->io[i].res->end;
|
|
|
|
|
|
|
|
s->ops->set_io_map(s, &io_off);
|
|
|
|
mdelay(40);
|
|
|
|
s->ops->set_io_map(s, &io_on);
|
2008-08-03 15:56:13 +07:00
|
|
|
}
|
2010-07-29 19:06:42 +07:00
|
|
|
unlock:
|
|
|
|
mutex_unlock(&s->ops_mutex);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-29 19:06:42 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_fixup_iowidth);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_fixup_vpp() - set Vpp to a new voltage level
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: pcmcia device
|
|
|
|
* @new_vpp: new Vpp voltage
|
2010-07-29 19:06:42 +07:00
|
|
|
*
|
|
|
|
* pcmcia_fixup_vpp() allows a PCMCIA device driver to set Vpp to
|
2010-07-30 00:27:09 +07:00
|
|
|
* a new voltage level between calls to pcmcia_enable_device()
|
2010-07-29 19:06:42 +07:00
|
|
|
* and pcmcia_disable_device().
|
|
|
|
*/
|
|
|
|
int pcmcia_fixup_vpp(struct pcmcia_device *p_dev, unsigned char new_vpp)
|
|
|
|
{
|
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
|
|
|
|
|
|
dev_dbg(&p_dev->dev, "fixup Vpp to %d\n", new_vpp);
|
|
|
|
|
|
|
|
if (!(s->state & SOCKET_PRESENT) ||
|
|
|
|
!(p_dev->function_config->state & CONFIG_LOCKED)) {
|
|
|
|
dev_dbg(&p_dev->dev, "No card? Config not locked?\n");
|
|
|
|
ret = -EACCES;
|
2010-03-16 21:57:44 +07:00
|
|
|
goto unlock;
|
2008-08-03 15:56:13 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-29 19:06:42 +07:00
|
|
|
s->socket.Vpp = new_vpp;
|
|
|
|
if (s->ops->set_socket(s, &s->socket)) {
|
|
|
|
dev_warn(&p_dev->dev, "Unable to set VPP\n");
|
|
|
|
ret = -EIO;
|
|
|
|
goto unlock;
|
2006-01-15 17:18:12 +07:00
|
|
|
}
|
2010-07-29 20:50:55 +07:00
|
|
|
p_dev->vpp = new_vpp;
|
2010-07-29 19:06:42 +07:00
|
|
|
|
2010-03-16 21:57:44 +07:00
|
|
|
unlock:
|
2010-01-18 00:31:34 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2006-01-15 17:18:12 +07:00
|
|
|
|
2010-03-16 21:57:44 +07:00
|
|
|
return ret;
|
2010-07-29 19:06:42 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_fixup_vpp);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_release_configuration() - physically disable a PCMCIA device
|
|
|
|
* @p_dev: pcmcia device
|
|
|
|
*
|
|
|
|
* pcmcia_release_configuration() is the 1:1 counterpart to
|
|
|
|
* pcmcia_enable_device(): If a PCMCIA device is no longer used by any
|
|
|
|
* driver, the Vpp voltage is set to 0, IRQs will no longer be generated,
|
|
|
|
* and I/O ranges will be disabled. As pcmcia_release_io() and
|
|
|
|
* pcmcia_release_window() still need to be called, device drivers are
|
|
|
|
* expected to call pcmcia_disable_device() instead.
|
|
|
|
*/
|
2005-07-08 07:59:02 +07:00
|
|
|
int pcmcia_release_configuration(struct pcmcia_device *p_dev)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
|
|
|
pccard_io_map io = { 0, 0, 0, 0, 1 };
|
2005-07-08 07:59:02 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2010-01-18 00:31:34 +07:00
|
|
|
config_t *c;
|
2005-06-28 06:28:53 +07:00
|
|
|
int i;
|
|
|
|
|
2010-01-16 06:26:33 +07:00
|
|
|
mutex_lock(&s->ops_mutex);
|
2010-01-18 00:31:34 +07:00
|
|
|
c = p_dev->function_config;
|
2006-03-02 06:09:29 +07:00
|
|
|
if (p_dev->_locked) {
|
|
|
|
p_dev->_locked = 0;
|
2005-06-28 06:28:53 +07:00
|
|
|
if (--(s->lock_count) == 0) {
|
2010-07-31 12:09:32 +07:00
|
|
|
s->socket.flags = SS_OUTPUT_ENA; /* Is this correct? */
|
2005-06-28 06:28:53 +07:00
|
|
|
s->socket.Vpp = 0;
|
|
|
|
s->socket.io_irq = 0;
|
|
|
|
s->ops->set_socket(s, &s->socket);
|
|
|
|
}
|
2006-01-15 15:32:39 +07:00
|
|
|
}
|
|
|
|
if (c->state & CONFIG_LOCKED) {
|
|
|
|
c->state &= ~CONFIG_LOCKED;
|
2005-06-28 06:28:53 +07:00
|
|
|
if (c->state & CONFIG_IO_REQ)
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
2006-01-15 14:04:43 +07:00
|
|
|
if (!s->io[i].res)
|
2005-06-28 06:28:53 +07:00
|
|
|
continue;
|
|
|
|
s->io[i].Config--;
|
|
|
|
if (s->io[i].Config != 0)
|
|
|
|
continue;
|
|
|
|
io.map = i;
|
|
|
|
s->ops->set_io_map(s, &io);
|
|
|
|
}
|
|
|
|
}
|
2010-01-16 06:26:33 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2008-08-03 15:07:45 +07:00
|
|
|
return 0;
|
2010-07-31 12:09:32 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_release_io() - release I/O allocated by a PCMCIA device
|
|
|
|
* @p_dev: pcmcia device
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_release_io() releases the I/O ranges allocated by a PCMCIA
|
|
|
|
* device. This may be invoked some time after a card ejection has
|
|
|
|
* already dumped the actual socket configuration, so if the client is
|
|
|
|
* "stale", we don't bother checking the port ranges against the
|
|
|
|
* current socket values.
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-07-24 18:14:44 +07:00
|
|
|
static int pcmcia_release_io(struct pcmcia_device *p_dev)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2005-07-08 07:59:02 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2010-01-18 00:31:34 +07:00
|
|
|
int ret = -EINVAL;
|
|
|
|
config_t *c;
|
|
|
|
|
|
|
|
mutex_lock(&s->ops_mutex);
|
2009-12-08 04:11:45 +07:00
|
|
|
if (!p_dev->_io)
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2006-01-15 15:32:39 +07:00
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
c = p_dev->function_config;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
release_io_space(s, &c->io[0]);
|
2006-01-15 15:32:39 +07:00
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
if (c->io[1].end)
|
|
|
|
release_io_space(s, &c->io[1]);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
p_dev->_io = 0;
|
|
|
|
c->state &= ~CONFIG_IO_REQ;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-01-18 00:31:34 +07:00
|
|
|
out:
|
|
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
|
|
|
|
return ret;
|
2005-06-28 06:28:53 +07:00
|
|
|
} /* pcmcia_release_io */
|
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
|
2010-07-28 15:59:06 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_release_window() - release reserved iomem for PCMCIA devices
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: pcmcia device
|
|
|
|
* @res: iomem resource to release
|
2010-07-28 15:59:06 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_release_window() releases &struct resource *res which was
|
2010-07-28 15:59:06 +07:00
|
|
|
* previously reserved by calling pcmcia_request_window().
|
|
|
|
*/
|
2010-07-25 00:03:02 +07:00
|
|
|
int pcmcia_release_window(struct pcmcia_device *p_dev, struct resource *res)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2006-12-13 17:46:53 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2009-11-03 07:16:12 +07:00
|
|
|
pccard_mem_map *win;
|
2010-07-25 00:03:02 +07:00
|
|
|
unsigned int w;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-25 00:03:02 +07:00
|
|
|
dev_dbg(&p_dev->dev, "releasing window %pR\n", res);
|
|
|
|
|
|
|
|
w = ((res->flags & IORESOURCE_BITS & WIN_FLAGS_REQ) >> 2) - 1;
|
|
|
|
if (w >= MAX_WIN)
|
2008-08-03 16:18:00 +07:00
|
|
|
return -EINVAL;
|
2006-12-13 17:46:53 +07:00
|
|
|
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_lock(&s->ops_mutex);
|
2010-07-25 00:03:02 +07:00
|
|
|
win = &s->win[w];
|
2006-12-13 17:46:53 +07:00
|
|
|
|
2010-07-25 00:03:02 +07:00
|
|
|
if (!(p_dev->_win & CLIENT_WIN_REQ(w))) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "not releasing unknown window\n");
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 16:18:00 +07:00
|
|
|
return -EINVAL;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Shut down memory window */
|
2009-11-03 07:16:12 +07:00
|
|
|
win->flags &= ~MAP_ACTIVE;
|
|
|
|
s->ops->set_mem_map(s, win);
|
2010-07-25 00:03:02 +07:00
|
|
|
s->state &= ~SOCKET_WIN_REQ(w);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Release system memory */
|
2009-11-03 07:16:12 +07:00
|
|
|
if (win->res) {
|
2010-07-25 18:10:22 +07:00
|
|
|
release_resource(res);
|
2009-11-03 07:16:12 +07:00
|
|
|
release_resource(win->res);
|
|
|
|
kfree(win->res);
|
|
|
|
win->res = NULL;
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2010-07-28 15:59:06 +07:00
|
|
|
res->start = res->end = 0;
|
|
|
|
res->flags = IORESOURCE_MEM;
|
2010-07-25 00:03:02 +07:00
|
|
|
p_dev->_win &= ~CLIENT_WIN_REQ(w);
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2008-08-03 15:07:45 +07:00
|
|
|
return 0;
|
2005-06-28 06:28:53 +07:00
|
|
|
} /* pcmcia_release_window */
|
|
|
|
EXPORT_SYMBOL(pcmcia_release_window);
|
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
|
2010-07-30 00:27:09 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_enable_device() - set up and activate a PCMCIA device
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: the associated PCMCIA device
|
2010-07-30 00:27:09 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_enable_device() physically enables a PCMCIA device. It parses
|
|
|
|
* the flags passed to in @flags and stored in @p_dev->flags and sets up
|
|
|
|
* the Vpp voltage, enables the speaker line, I/O ports and store proper
|
|
|
|
* values to configuration registers.
|
2010-07-30 00:27:09 +07:00
|
|
|
*/
|
|
|
|
int pcmcia_enable_device(struct pcmcia_device *p_dev)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
|
|
|
int i;
|
2010-07-30 00:27:09 +07:00
|
|
|
unsigned int base;
|
2005-07-08 07:59:02 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2005-06-28 06:28:53 +07:00
|
|
|
config_t *c;
|
|
|
|
pccard_io_map iomap;
|
2010-07-29 21:19:39 +07:00
|
|
|
unsigned char status = 0;
|
|
|
|
unsigned char ext_status = 0;
|
2010-07-29 23:35:47 +07:00
|
|
|
unsigned char option = 0;
|
2010-07-30 00:27:09 +07:00
|
|
|
unsigned int flags = p_dev->config_flags;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
if (!(s->state & SOCKET_PRESENT))
|
2009-06-28 23:26:41 +07:00
|
|
|
return -ENODEV;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-01-18 00:31:34 +07:00
|
|
|
mutex_lock(&s->ops_mutex);
|
2006-01-11 02:41:27 +07:00
|
|
|
c = p_dev->function_config;
|
2009-10-24 17:20:18 +07:00
|
|
|
if (c->state & CONFIG_LOCKED) {
|
2010-01-18 00:31:34 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "Configuration is locked\n");
|
2008-08-03 16:23:42 +07:00
|
|
|
return -EACCES;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Do power control. We don't allow changes in Vcc. */
|
2010-07-29 20:50:55 +07:00
|
|
|
s->socket.Vpp = p_dev->vpp;
|
2008-08-03 15:56:13 +07:00
|
|
|
if (s->ops->set_socket(s, &s->socket)) {
|
2010-01-16 06:26:33 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_printk(KERN_WARNING, &p_dev->dev,
|
2008-08-03 15:56:13 +07:00
|
|
|
"Unable to set socket state\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Pick memory or I/O card, DMA mode, interrupt */
|
2010-10-22 13:46:36 +07:00
|
|
|
if (p_dev->_io || flags & CONF_ENABLE_IRQ)
|
|
|
|
flags |= CONF_ENABLE_IOCARD;
|
|
|
|
if (flags & CONF_ENABLE_IOCARD)
|
2005-06-28 06:28:53 +07:00
|
|
|
s->socket.flags |= SS_IOCARD;
|
2011-02-19 18:35:15 +07:00
|
|
|
if (flags & CONF_ENABLE_ZVCARD)
|
|
|
|
s->socket.flags |= SS_ZVCARD | SS_IOCARD;
|
2010-07-30 00:27:09 +07:00
|
|
|
if (flags & CONF_ENABLE_SPKR) {
|
2005-06-28 06:28:53 +07:00
|
|
|
s->socket.flags |= SS_SPKR_ENA;
|
2010-07-29 21:19:39 +07:00
|
|
|
status = CCSR_AUDIO_ENA;
|
2010-07-29 23:35:47 +07:00
|
|
|
if (!(p_dev->config_regs & PRESENT_STATUS))
|
2010-07-29 21:19:39 +07:00
|
|
|
dev_warn(&p_dev->dev, "speaker requested, but "
|
|
|
|
"PRESENT_STATUS not set!\n");
|
|
|
|
}
|
2010-07-30 00:27:09 +07:00
|
|
|
if (flags & CONF_ENABLE_IRQ)
|
2010-03-07 16:51:23 +07:00
|
|
|
s->socket.io_irq = s->pcmcia_irq;
|
2005-06-28 06:28:53 +07:00
|
|
|
else
|
|
|
|
s->socket.io_irq = 0;
|
2010-07-30 00:27:09 +07:00
|
|
|
if (flags & CONF_ENABLE_ESR) {
|
2010-07-29 23:35:47 +07:00
|
|
|
p_dev->config_regs |= PRESENT_EXT_STATUS;
|
2010-07-29 21:19:39 +07:00
|
|
|
ext_status = ESR_REQ_ATTN_ENA;
|
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
s->ops->set_socket(s, &s->socket);
|
|
|
|
s->lock_count++;
|
|
|
|
|
2010-09-19 04:21:09 +07:00
|
|
|
dev_dbg(&p_dev->dev,
|
|
|
|
"enable_device: V %d, flags %x, base %x, regs %x, idx %x\n",
|
|
|
|
p_dev->vpp, flags, p_dev->config_base, p_dev->config_regs,
|
|
|
|
p_dev->config_index);
|
|
|
|
|
2005-06-28 06:28:53 +07:00
|
|
|
/* Set up CIS configuration registers */
|
2010-07-29 23:35:47 +07:00
|
|
|
base = p_dev->config_base;
|
|
|
|
if (p_dev->config_regs & PRESENT_COPY) {
|
2010-07-29 20:54:54 +07:00
|
|
|
u16 tmp = 0;
|
|
|
|
dev_dbg(&p_dev->dev, "clearing CISREG_SCR\n");
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &tmp);
|
|
|
|
}
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_PIN_REPLACE) {
|
2010-07-29 20:54:54 +07:00
|
|
|
u16 tmp = 0;
|
|
|
|
dev_dbg(&p_dev->dev, "clearing CISREG_PRR\n");
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &tmp);
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_OPTION) {
|
2005-06-28 06:28:53 +07:00
|
|
|
if (s->functions == 1) {
|
2010-07-29 23:35:47 +07:00
|
|
|
option = p_dev->config_index & COR_CONFIG_MASK;
|
2005-06-28 06:28:53 +07:00
|
|
|
} else {
|
2010-07-29 23:35:47 +07:00
|
|
|
option = p_dev->config_index & COR_MFC_CONFIG_MASK;
|
|
|
|
option |= COR_FUNC_ENA|COR_IREQ_ENA;
|
|
|
|
if (p_dev->config_regs & PRESENT_IOBASE_0)
|
|
|
|
option |= COR_ADDR_DECODE;
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2010-07-30 00:27:09 +07:00
|
|
|
if ((flags & CONF_ENABLE_IRQ) &&
|
|
|
|
!(flags & CONF_ENABLE_PULSE_IRQ))
|
2010-07-29 23:35:47 +07:00
|
|
|
option |= COR_LEVEL_REQ;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &option);
|
2005-06-28 06:28:53 +07:00
|
|
|
mdelay(40);
|
|
|
|
}
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_STATUS)
|
2010-07-29 21:19:39 +07:00
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &status);
|
|
|
|
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_EXT_STATUS)
|
2010-07-29 21:19:39 +07:00
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1,
|
|
|
|
&ext_status);
|
|
|
|
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_IOBASE_0) {
|
2010-07-24 18:14:44 +07:00
|
|
|
u8 b = c->io[0].start & 0xff;
|
2005-06-28 06:28:53 +07:00
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &b);
|
2010-07-24 18:14:44 +07:00
|
|
|
b = (c->io[0].start >> 8) & 0xff;
|
2005-06-28 06:28:53 +07:00
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &b);
|
|
|
|
}
|
2010-07-29 23:35:47 +07:00
|
|
|
if (p_dev->config_regs & PRESENT_IOSIZE) {
|
2010-07-24 18:14:44 +07:00
|
|
|
u8 b = resource_size(&c->io[0]) + resource_size(&c->io[1]) - 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_IOSIZE)>>1, 1, &b);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Configure I/O windows */
|
|
|
|
if (c->state & CONFIG_IO_REQ) {
|
|
|
|
iomap.speed = io_speed;
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++)
|
2006-01-15 14:04:43 +07:00
|
|
|
if (s->io[i].res) {
|
2005-06-28 06:28:53 +07:00
|
|
|
iomap.map = i;
|
|
|
|
iomap.flags = MAP_ACTIVE;
|
2006-01-15 14:04:43 +07:00
|
|
|
switch (s->io[i].res->flags & IO_DATA_PATH_WIDTH) {
|
2005-06-28 06:28:53 +07:00
|
|
|
case IO_DATA_PATH_WIDTH_16:
|
|
|
|
iomap.flags |= MAP_16BIT; break;
|
|
|
|
case IO_DATA_PATH_WIDTH_AUTO:
|
|
|
|
iomap.flags |= MAP_AUTOSZ; break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2006-01-15 14:04:43 +07:00
|
|
|
iomap.start = s->io[i].res->start;
|
|
|
|
iomap.stop = s->io[i].res->end;
|
2005-06-28 06:28:53 +07:00
|
|
|
s->ops->set_io_map(s, &iomap);
|
|
|
|
s->io[i].Config++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c->state |= CONFIG_LOCKED;
|
2006-03-02 06:09:29 +07:00
|
|
|
p_dev->_locked = 1;
|
2010-03-30 23:07:50 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 15:07:45 +07:00
|
|
|
return 0;
|
2010-07-30 00:27:09 +07:00
|
|
|
} /* pcmcia_enable_device */
|
|
|
|
EXPORT_SYMBOL(pcmcia_enable_device);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_request_io() - attempt to reserve port ranges for PCMCIA devices
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: the associated PCMCIA device
|
2010-07-24 18:14:44 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_request_io() attempts to reserve the IO port ranges specified in
|
2010-07-24 22:23:51 +07:00
|
|
|
* &struct pcmcia_device @p_dev->resource[0] and @p_dev->resource[1]. The
|
2010-07-24 18:14:44 +07:00
|
|
|
* "start" value is the requested start of the IO port resource; "end"
|
2010-07-24 22:23:51 +07:00
|
|
|
* reflects the number of ports requested. The number of IO lines requested
|
|
|
|
* is specified in &struct pcmcia_device @p_dev->io_lines.
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-07-24 22:23:51 +07:00
|
|
|
int pcmcia_request_io(struct pcmcia_device *p_dev)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2005-07-08 07:59:02 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2010-07-24 22:23:51 +07:00
|
|
|
config_t *c = p_dev->function_config;
|
2010-01-18 00:31:34 +07:00
|
|
|
int ret = -EINVAL;
|
|
|
|
|
|
|
|
mutex_lock(&s->ops_mutex);
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "pcmcia_request_io: %pR , %pR",
|
|
|
|
&c->io[0], &c->io[1]);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2009-10-24 17:20:18 +07:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "pcmcia_request_io: No card present\n");
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2009-10-24 17:20:18 +07:00
|
|
|
if (c->state & CONFIG_LOCKED) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "Configuration is locked\n");
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2008-08-03 16:40:19 +07:00
|
|
|
if (c->state & CONFIG_IO_REQ) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "IO already configured\n");
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2008-08-03 16:40:19 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-24 22:23:51 +07:00
|
|
|
ret = alloc_io_space(s, &c->io[0], p_dev->io_lines);
|
2010-07-24 18:14:44 +07:00
|
|
|
if (ret)
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-07-24 18:14:44 +07:00
|
|
|
if (c->io[1].end) {
|
2010-07-24 22:23:51 +07:00
|
|
|
ret = alloc_io_space(s, &c->io[1], p_dev->io_lines);
|
2010-01-18 00:31:34 +07:00
|
|
|
if (ret) {
|
2010-09-18 15:19:13 +07:00
|
|
|
struct resource tmp = c->io[0];
|
|
|
|
/* release the previously allocated resource */
|
2010-07-24 18:14:44 +07:00
|
|
|
release_io_space(s, &c->io[0]);
|
2010-09-18 15:19:13 +07:00
|
|
|
/* but preserve the settings, for they worked... */
|
|
|
|
c->io[0].end = resource_size(&tmp);
|
|
|
|
c->io[0].start = tmp.start;
|
|
|
|
c->io[0].flags = tmp.flags;
|
2010-01-18 00:31:34 +07:00
|
|
|
goto out;
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2010-07-24 18:14:44 +07:00
|
|
|
} else
|
|
|
|
c->io[1].start = 0;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
c->state |= CONFIG_IO_REQ;
|
2006-03-02 06:09:29 +07:00
|
|
|
p_dev->_io = 1;
|
2010-01-18 00:31:34 +07:00
|
|
|
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "pcmcia_request_io succeeded: %pR , %pR",
|
2010-07-24 18:14:44 +07:00
|
|
|
&c->io[0], &c->io[1]);
|
2010-01-18 00:31:34 +07:00
|
|
|
out:
|
|
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
|
|
|
|
return ret;
|
2005-06-28 06:28:53 +07:00
|
|
|
} /* pcmcia_request_io */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_io);
|
|
|
|
|
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_request_irq() - attempt to request a IRQ for a PCMCIA device
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: the associated PCMCIA device
|
|
|
|
* @handler: IRQ handler to register
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_request_irq() is a wrapper around request_irq() which allows
|
2010-03-07 18:21:16 +07:00
|
|
|
* the PCMCIA core to clean up the registration in pcmcia_disable_device().
|
|
|
|
* Drivers are free to use request_irq() directly, but then they need to
|
2010-07-31 12:09:32 +07:00
|
|
|
* call free_irq() themselfves, too. Also, only %IRQF_SHARED capable IRQ
|
2010-03-07 18:21:16 +07:00
|
|
|
* handlers are allowed.
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-03-07 18:21:16 +07:00
|
|
|
int __must_check pcmcia_request_irq(struct pcmcia_device *p_dev,
|
|
|
|
irq_handler_t handler)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2010-03-07 18:21:16 +07:00
|
|
|
int ret;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
if (!p_dev->irq)
|
|
|
|
return -EINVAL;
|
2010-01-18 00:31:34 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
ret = request_irq(p_dev->irq, handler, IRQF_SHARED,
|
|
|
|
p_dev->devname, p_dev->priv);
|
|
|
|
if (!ret)
|
|
|
|
p_dev->_irq = 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_irq);
|
2010-04-09 01:33:16 +07:00
|
|
|
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_request_exclusive_irq() - attempt to request an exclusive IRQ first
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: the associated PCMCIA device
|
|
|
|
* @handler: IRQ handler to register
|
2010-03-07 18:21:16 +07:00
|
|
|
*
|
2010-07-31 12:09:32 +07:00
|
|
|
* pcmcia_request_exclusive_irq() is a wrapper around request_irq() which
|
2010-03-07 18:21:16 +07:00
|
|
|
* attempts first to request an exclusive IRQ. If it fails, it also accepts
|
|
|
|
* a shared IRQ, but prints out a warning. PCMCIA drivers should allow for
|
|
|
|
* IRQ sharing and either use request_irq directly (then they need to call
|
2010-07-31 12:09:32 +07:00
|
|
|
* free_irq() themselves, too), or the pcmcia_request_irq() function.
|
2010-03-07 18:21:16 +07:00
|
|
|
*/
|
|
|
|
int __must_check
|
2010-03-20 19:10:47 +07:00
|
|
|
__pcmcia_request_exclusive_irq(struct pcmcia_device *p_dev,
|
|
|
|
irq_handler_t handler)
|
2010-03-07 18:21:16 +07:00
|
|
|
{
|
|
|
|
int ret;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
if (!p_dev->irq)
|
|
|
|
return -EINVAL;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
ret = request_irq(p_dev->irq, handler, 0, p_dev->devname, p_dev->priv);
|
|
|
|
if (ret) {
|
|
|
|
ret = pcmcia_request_irq(p_dev, handler);
|
|
|
|
dev_printk(KERN_WARNING, &p_dev->dev, "pcmcia: "
|
|
|
|
"request for exclusive IRQ could not be fulfilled.\n");
|
|
|
|
dev_printk(KERN_WARNING, &p_dev->dev, "pcmcia: the driver "
|
|
|
|
"needs updating to supported shared IRQ lines.\n");
|
|
|
|
}
|
|
|
|
if (ret)
|
|
|
|
dev_printk(KERN_INFO, &p_dev->dev, "request_irq() failed\n");
|
|
|
|
else
|
|
|
|
p_dev->_irq = 1;
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2010-01-18 00:31:34 +07:00
|
|
|
return ret;
|
2010-03-07 18:21:16 +07:00
|
|
|
} /* pcmcia_request_exclusive_irq */
|
2010-03-20 19:10:47 +07:00
|
|
|
EXPORT_SYMBOL(__pcmcia_request_exclusive_irq);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
|
2010-04-09 01:33:16 +07:00
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
|
|
|
|
|
|
|
/* mask of IRQs already reserved by other cards, we should avoid using them */
|
2010-08-03 14:33:45 +07:00
|
|
|
static u8 pcmcia_used_irq[32];
|
2010-04-09 01:33:16 +07:00
|
|
|
|
|
|
|
static irqreturn_t test_action(int cpl, void *dev_id)
|
|
|
|
{
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_setup_isa_irq() - determine whether an ISA IRQ can be used
|
|
|
|
* @p_dev - the associated PCMCIA device
|
|
|
|
*
|
|
|
|
* locking note: must be called with ops_mutex locked.
|
|
|
|
*/
|
|
|
|
static int pcmcia_setup_isa_irq(struct pcmcia_device *p_dev, int type)
|
|
|
|
{
|
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
|
|
|
unsigned int try, irq;
|
|
|
|
u32 mask = s->irq_mask;
|
|
|
|
int ret = -ENODEV;
|
|
|
|
|
|
|
|
for (try = 0; try < 64; try++) {
|
|
|
|
irq = try % 32;
|
|
|
|
|
2010-08-03 14:33:45 +07:00
|
|
|
if (irq > NR_IRQS)
|
|
|
|
continue;
|
|
|
|
|
2010-04-09 01:33:16 +07:00
|
|
|
/* marked as available by driver, not blocked by userspace? */
|
|
|
|
if (!((mask >> irq) & 1))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* avoid an IRQ which is already used by another PCMCIA card */
|
|
|
|
if ((try < 32) && pcmcia_used_irq[irq])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* register the correct driver, if possible, to check whether
|
|
|
|
* registering a dummy handle works, i.e. if the IRQ isn't
|
|
|
|
* marked as used by the kernel resource management core */
|
|
|
|
ret = request_irq(irq, test_action, type, p_dev->devname,
|
|
|
|
p_dev);
|
|
|
|
if (!ret) {
|
|
|
|
free_irq(irq, p_dev);
|
2010-03-07 18:21:16 +07:00
|
|
|
p_dev->irq = s->pcmcia_irq = irq;
|
2010-04-09 01:33:16 +07:00
|
|
|
pcmcia_used_irq[irq]++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcmcia_cleanup_irq(struct pcmcia_socket *s)
|
|
|
|
{
|
2010-03-07 16:51:23 +07:00
|
|
|
pcmcia_used_irq[s->pcmcia_irq]--;
|
|
|
|
s->pcmcia_irq = 0;
|
2010-04-09 01:33:16 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
#else /* CONFIG_PCMCIA_PROBE */
|
|
|
|
|
|
|
|
static int pcmcia_setup_isa_irq(struct pcmcia_device *p_dev, int type)
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pcmcia_cleanup_irq(struct pcmcia_socket *s)
|
|
|
|
{
|
2010-03-07 16:51:23 +07:00
|
|
|
s->pcmcia_irq = 0;
|
2010-04-09 01:33:16 +07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_PCMCIA_PROBE */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_setup_irq() - determine IRQ to be used for device
|
|
|
|
* @p_dev - the associated PCMCIA device
|
|
|
|
*
|
|
|
|
* locking note: must be called with ops_mutex locked.
|
|
|
|
*/
|
|
|
|
int pcmcia_setup_irq(struct pcmcia_device *p_dev)
|
|
|
|
{
|
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
|
|
|
|
2010-03-07 18:21:16 +07:00
|
|
|
if (p_dev->irq)
|
2010-04-09 01:33:16 +07:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* already assigned? */
|
2010-03-07 16:51:23 +07:00
|
|
|
if (s->pcmcia_irq) {
|
2010-03-07 18:21:16 +07:00
|
|
|
p_dev->irq = s->pcmcia_irq;
|
2010-04-09 01:33:16 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* prefer an exclusive ISA irq */
|
|
|
|
if (!pcmcia_setup_isa_irq(p_dev, 0))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* but accept a shared ISA irq */
|
|
|
|
if (!pcmcia_setup_isa_irq(p_dev, IRQF_SHARED))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* but use the PCI irq otherwise */
|
|
|
|
if (s->pci_irq) {
|
2010-03-07 18:21:16 +07:00
|
|
|
p_dev->irq = s->pcmcia_irq = s->pci_irq;
|
2010-04-09 01:33:16 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-07-28 15:59:06 +07:00
|
|
|
/**
|
|
|
|
* pcmcia_request_window() - attempt to reserve iomem for PCMCIA devices
|
2010-07-31 12:09:32 +07:00
|
|
|
* @p_dev: the associated PCMCIA device
|
|
|
|
* @res: &struct resource pointing to p_dev->resource[2..5]
|
|
|
|
* @speed: access speed
|
2005-06-28 06:28:53 +07:00
|
|
|
*
|
2010-07-28 15:59:06 +07:00
|
|
|
* pcmcia_request_window() attepts to reserve an iomem ranges specified in
|
2010-07-31 12:09:32 +07:00
|
|
|
* &struct resource @res pointing to one of the entries in
|
|
|
|
* &struct pcmcia_device @p_dev->resource[2..5]. The "start" value is the
|
2010-07-28 15:59:06 +07:00
|
|
|
* requested start of the IO mem resource; "end" reflects the size
|
|
|
|
* requested.
|
2005-06-28 06:28:53 +07:00
|
|
|
*/
|
2010-07-28 15:59:06 +07:00
|
|
|
int pcmcia_request_window(struct pcmcia_device *p_dev, struct resource *res,
|
|
|
|
unsigned int speed)
|
2005-06-28 06:28:53 +07:00
|
|
|
{
|
2009-11-03 07:31:52 +07:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2009-11-03 07:16:12 +07:00
|
|
|
pccard_mem_map *win;
|
2005-06-28 06:28:53 +07:00
|
|
|
u_long align;
|
|
|
|
int w;
|
|
|
|
|
2010-09-19 04:21:09 +07:00
|
|
|
dev_dbg(&p_dev->dev, "request_window %pR %d\n", res, speed);
|
|
|
|
|
2009-10-24 17:20:18 +07:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "No card present\n");
|
2008-08-03 16:10:56 +07:00
|
|
|
return -ENODEV;
|
2009-10-24 17:20:18 +07:00
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Window size defaults to smallest available */
|
2010-07-28 15:59:06 +07:00
|
|
|
if (res->end == 0)
|
|
|
|
res->end = s->map_size;
|
|
|
|
align = (s->features & SS_CAP_MEM_ALIGN) ? res->end : s->map_size;
|
|
|
|
if (res->end & (s->map_size-1)) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "invalid map size\n");
|
2008-08-03 17:10:53 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2010-07-28 15:59:06 +07:00
|
|
|
if ((res->start && (s->features & SS_CAP_STATIC_MAP)) ||
|
|
|
|
(res->start & (align-1))) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "invalid base address\n");
|
2008-08-03 17:10:53 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2010-07-28 15:59:06 +07:00
|
|
|
if (res->start)
|
2005-06-28 06:28:53 +07:00
|
|
|
align = 0;
|
|
|
|
|
|
|
|
/* Allocate system memory window */
|
2010-07-25 00:03:02 +07:00
|
|
|
mutex_lock(&s->ops_mutex);
|
2005-06-28 06:28:53 +07:00
|
|
|
for (w = 0; w < MAX_WIN; w++)
|
2009-12-08 04:11:45 +07:00
|
|
|
if (!(s->state & SOCKET_WIN_REQ(w)))
|
|
|
|
break;
|
2008-08-03 16:40:19 +07:00
|
|
|
if (w == MAX_WIN) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "all windows are used already\n");
|
2010-07-25 00:03:02 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 16:40:19 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
win = &s->win[w];
|
|
|
|
|
|
|
|
if (!(s->features & SS_CAP_STATIC_MAP)) {
|
2010-07-28 15:59:06 +07:00
|
|
|
win->res = pcmcia_find_mem_region(res->start, res->end, align,
|
2010-07-25 00:03:02 +07:00
|
|
|
0, s);
|
2009-11-03 07:16:12 +07:00
|
|
|
if (!win->res) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "allocating mem region failed\n");
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 16:40:19 +07:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
}
|
2009-11-03 07:31:52 +07:00
|
|
|
p_dev->_win |= CLIENT_WIN_REQ(w);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
|
|
|
/* Configure the socket controller */
|
2009-11-03 07:16:12 +07:00
|
|
|
win->map = w+1;
|
2010-07-28 15:59:06 +07:00
|
|
|
win->flags = res->flags & WIN_FLAGS_MAP;
|
|
|
|
win->speed = speed;
|
2009-11-03 07:16:12 +07:00
|
|
|
win->card_start = 0;
|
2010-01-13 03:42:51 +07:00
|
|
|
|
2009-11-03 07:16:12 +07:00
|
|
|
if (s->ops->set_mem_map(s, win) != 0) {
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "failed to set memory mapping\n");
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2008-08-03 17:15:11 +07:00
|
|
|
return -EIO;
|
|
|
|
}
|
2005-06-28 06:28:53 +07:00
|
|
|
s->state |= SOCKET_WIN_REQ(w);
|
|
|
|
|
|
|
|
/* Return window handle */
|
2009-12-08 04:11:45 +07:00
|
|
|
if (s->features & SS_CAP_STATIC_MAP)
|
2010-07-28 15:59:06 +07:00
|
|
|
res->start = win->static_start;
|
2009-12-08 04:11:45 +07:00
|
|
|
else
|
2010-07-28 15:59:06 +07:00
|
|
|
res->start = win->res->start;
|
2009-12-08 04:11:45 +07:00
|
|
|
|
2010-07-25 00:03:02 +07:00
|
|
|
/* convert to new-style resources */
|
2010-07-28 15:59:06 +07:00
|
|
|
res->end += res->start - 1;
|
|
|
|
res->flags &= ~WIN_FLAGS_REQ;
|
|
|
|
res->flags |= (win->map << 2) | IORESOURCE_MEM;
|
2010-07-25 18:10:22 +07:00
|
|
|
res->parent = win->res;
|
|
|
|
if (win->res)
|
|
|
|
request_resource(&iomem_resource, res);
|
|
|
|
|
2010-09-13 21:51:36 +07:00
|
|
|
dev_dbg(&p_dev->dev, "request_window results in %pR\n", res);
|
2010-07-25 00:03:02 +07:00
|
|
|
|
2010-01-13 03:42:51 +07:00
|
|
|
mutex_unlock(&s->ops_mutex);
|
2005-06-28 06:28:53 +07:00
|
|
|
|
2008-08-03 15:07:45 +07:00
|
|
|
return 0;
|
2005-06-28 06:28:53 +07:00
|
|
|
} /* pcmcia_request_window */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_window);
|
2006-01-15 15:32:39 +07:00
|
|
|
|
2010-07-31 12:09:32 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_disable_device() - disable and clean up a PCMCIA device
|
|
|
|
* @p_dev: the associated PCMCIA device
|
|
|
|
*
|
|
|
|
* pcmcia_disable_device() is the driver-callable counterpart to
|
|
|
|
* pcmcia_enable_device(): If a PCMCIA device is no longer used,
|
|
|
|
* drivers are expected to clean up and disable the device by calling
|
|
|
|
* this function. Any I/O ranges (iomem and ioports) will be released,
|
|
|
|
* the Vpp voltage will be set to 0, and IRQs will no longer be
|
|
|
|
* generated -- at least if there is no other card function (of
|
|
|
|
* multifunction devices) being used.
|
|
|
|
*/
|
2009-12-08 04:11:45 +07:00
|
|
|
void pcmcia_disable_device(struct pcmcia_device *p_dev)
|
|
|
|
{
|
2010-07-25 00:03:02 +07:00
|
|
|
int i;
|
2010-09-19 04:21:09 +07:00
|
|
|
|
|
|
|
dev_dbg(&p_dev->dev, "disabling device\n");
|
|
|
|
|
2010-07-25 00:03:02 +07:00
|
|
|
for (i = 0; i < MAX_WIN; i++) {
|
|
|
|
struct resource *res = p_dev->resource[MAX_IO_WIN + i];
|
|
|
|
if (res->flags & WIN_FLAGS_REQ)
|
|
|
|
pcmcia_release_window(p_dev, res);
|
|
|
|
}
|
|
|
|
|
2006-01-15 15:32:39 +07:00
|
|
|
pcmcia_release_configuration(p_dev);
|
2010-07-24 18:14:44 +07:00
|
|
|
pcmcia_release_io(p_dev);
|
2010-07-21 05:21:42 +07:00
|
|
|
if (p_dev->_irq) {
|
2010-03-07 18:21:16 +07:00
|
|
|
free_irq(p_dev->irq, p_dev->priv);
|
2010-07-21 05:21:42 +07:00
|
|
|
p_dev->_irq = 0;
|
|
|
|
}
|
2006-01-15 15:32:39 +07:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_disable_device);
|