2005-06-28 07:28:53 +08: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
|
|
|
|
* Copyright (C) 2004-2005 Dominik Brodowski
|
|
|
|
*
|
|
|
|
* 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 05:32:33 +08:00
|
|
|
#include <linux/netdevice.h>
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
#include <pcmcia/cs_types.h>
|
|
|
|
#include <pcmcia/ss.h>
|
|
|
|
#include <pcmcia/cs.h>
|
|
|
|
#include <pcmcia/cistpl.h>
|
|
|
|
#include <pcmcia/cisreg.h>
|
|
|
|
#include <pcmcia/ds.h>
|
|
|
|
|
|
|
|
#include "cs_internal.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Access speed for IO windows */
|
|
|
|
static int io_speed = 0;
|
|
|
|
module_param(io_speed, int, 0444);
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
2005-08-24 05:47:01 +08:00
|
|
|
#include <asm/irq.h>
|
2005-06-28 07:28:53 +08:00
|
|
|
/* mask of IRQs already reserved by other cards, we should avoid using them */
|
|
|
|
static u8 pcmcia_used_irq[NR_IRQS];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/** alloc_io_space
|
|
|
|
*
|
|
|
|
* Special stuff for managing IO windows, because they are scarce
|
|
|
|
*/
|
|
|
|
|
2008-02-05 14:27:34 +08:00
|
|
|
static int alloc_io_space(struct pcmcia_socket *s, u_int attr,
|
|
|
|
unsigned int *base, unsigned int num, u_int lines)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
|
|
|
int i;
|
2008-02-05 14:27:34 +08:00
|
|
|
unsigned int try, align;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
align = (*base) ? (lines ? 1<<lines : 0) : 1;
|
|
|
|
if (align && (align < num)) {
|
|
|
|
if (*base) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "odd IO request: num %#x align %#x\n",
|
2005-06-28 07:28:53 +08:00
|
|
|
num, align);
|
|
|
|
align = 0;
|
|
|
|
} else
|
|
|
|
while (align && (align < num)) align <<= 1;
|
|
|
|
}
|
|
|
|
if (*base & ~(align-1)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "odd IO request: base %#x align %#x\n",
|
2005-06-28 07:28:53 +08:00
|
|
|
*base, align);
|
|
|
|
align = 0;
|
|
|
|
}
|
|
|
|
if ((s->features & SS_CAP_STATIC_MAP) && s->io_offset) {
|
|
|
|
*base = s->io_offset | (*base & 0x0fff);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Check for an already-allocated window that must conflict with
|
|
|
|
* what was asked for. It is a hack because it does not catch all
|
|
|
|
* potential conflicts, just the most obvious ones.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++)
|
2006-10-21 05:44:09 +08:00
|
|
|
if ((s->io[i].res) && *base &&
|
2006-01-15 15:04:43 +08:00
|
|
|
((s->io[i].res->start & (align-1)) == *base))
|
2005-06-28 07:28:53 +08:00
|
|
|
return 1;
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
2006-01-15 15:04:43 +08:00
|
|
|
if (!s->io[i].res) {
|
2005-06-28 07:28:53 +08:00
|
|
|
s->io[i].res = pcmcia_find_io_region(*base, num, align, s);
|
|
|
|
if (s->io[i].res) {
|
2006-01-15 15:04:43 +08:00
|
|
|
*base = s->io[i].res->start;
|
|
|
|
s->io[i].res->flags = (s->io[i].res->flags & ~IORESOURCE_BITS) | (attr & IORESOURCE_BITS);
|
|
|
|
s->io[i].InUse = num;
|
2005-06-28 07:28:53 +08:00
|
|
|
break;
|
|
|
|
} else
|
|
|
|
return 1;
|
2006-01-15 15:04:43 +08:00
|
|
|
} else if ((s->io[i].res->flags & IORESOURCE_BITS) != (attr & IORESOURCE_BITS))
|
2005-06-28 07:28:53 +08:00
|
|
|
continue;
|
|
|
|
/* Try to extend top of window */
|
2006-01-15 15:04:43 +08:00
|
|
|
try = s->io[i].res->end + 1;
|
2005-06-28 07:28:53 +08:00
|
|
|
if ((*base == 0) || (*base == try))
|
|
|
|
if (pcmcia_adjust_io_region(s->io[i].res, s->io[i].res->start,
|
|
|
|
s->io[i].res->end + num, s) == 0) {
|
|
|
|
*base = try;
|
|
|
|
s->io[i].InUse += num;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Try to extend bottom of window */
|
2006-01-15 15:04:43 +08:00
|
|
|
try = s->io[i].res->start - num;
|
2005-06-28 07:28:53 +08:00
|
|
|
if ((*base == 0) || (*base == try))
|
|
|
|
if (pcmcia_adjust_io_region(s->io[i].res, s->io[i].res->start - num,
|
|
|
|
s->io[i].res->end, s) == 0) {
|
2006-01-15 15:04:43 +08:00
|
|
|
*base = try;
|
2005-06-28 07:28:53 +08:00
|
|
|
s->io[i].InUse += num;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (i == MAX_IO_WIN);
|
|
|
|
} /* alloc_io_space */
|
|
|
|
|
|
|
|
|
2008-02-05 14:27:34 +08:00
|
|
|
static void release_io_space(struct pcmcia_socket *s, unsigned int base,
|
|
|
|
unsigned int num)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
2006-01-15 15:04:43 +08:00
|
|
|
if (!s->io[i].res)
|
|
|
|
continue;
|
|
|
|
if ((s->io[i].res->start <= base) &&
|
|
|
|
(s->io[i].res->end >= base+num-1)) {
|
2005-06-28 07:28:53 +08:00
|
|
|
s->io[i].InUse -= num;
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} /* release_io_space */
|
|
|
|
|
|
|
|
|
|
|
|
/** pccard_access_configuration_register
|
|
|
|
*
|
|
|
|
* Access_configuration_register() reads and writes configuration
|
|
|
|
* registers in attribute memory. Memory window 0 is reserved for
|
|
|
|
* this and the tuple reading services.
|
|
|
|
*/
|
|
|
|
|
2006-01-11 03:48:59 +08:00
|
|
|
int pcmcia_access_configuration_register(struct pcmcia_device *p_dev,
|
2005-06-28 07:28:53 +08:00
|
|
|
conf_reg_t *reg)
|
|
|
|
{
|
2006-01-11 03:48:59 +08:00
|
|
|
struct pcmcia_socket *s;
|
2005-06-28 07:28:53 +08:00
|
|
|
config_t *c;
|
|
|
|
int addr;
|
|
|
|
u_char val;
|
|
|
|
|
2006-01-11 03:48:59 +08:00
|
|
|
if (!p_dev || !p_dev->function_config)
|
2008-08-03 17:10:56 +08:00
|
|
|
return -EINVAL;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-01-11 03:48:59 +08:00
|
|
|
s = p_dev->socket;
|
|
|
|
c = p_dev->function_config;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!(c->state & CONFIG_LOCKED)) {
|
|
|
|
dev_dbg(&s->dev, "Configuration isnt't locked\n");
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
addr = (c->ConfigBase + reg->Offset) >> 1;
|
|
|
|
|
|
|
|
switch (reg->Action) {
|
|
|
|
case CS_READ:
|
|
|
|
pcmcia_read_cis_mem(s, 1, addr, 1, &val);
|
|
|
|
reg->Value = val;
|
|
|
|
break;
|
|
|
|
case CS_WRITE:
|
|
|
|
val = reg->Value;
|
|
|
|
pcmcia_write_cis_mem(s, 1, addr, 1, &val);
|
|
|
|
break;
|
|
|
|
default:
|
2009-10-24 18:20:18 +08:00
|
|
|
dev_dbg(&s->dev, "Invalid conf register request\n");
|
2008-08-03 18:15:11 +08:00
|
|
|
return -EINVAL;
|
2005-06-28 07:28:53 +08:00
|
|
|
break;
|
|
|
|
}
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2006-01-11 03:48:59 +08:00
|
|
|
} /* pcmcia_access_configuration_register */
|
2005-06-28 07:28:54 +08:00
|
|
|
EXPORT_SYMBOL(pcmcia_access_configuration_register);
|
|
|
|
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-12-13 18:46:43 +08:00
|
|
|
int pcmcia_map_mem_page(struct pcmcia_device *p_dev, window_handle_t wh,
|
|
|
|
memreq_t *req)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2006-12-13 18:46:53 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2006-12-13 18:46:43 +08:00
|
|
|
|
2006-12-13 18:46:53 +08:00
|
|
|
wh--;
|
|
|
|
if (wh >= MAX_WIN)
|
2008-08-03 17:18:00 +08:00
|
|
|
return -EINVAL;
|
2008-08-03 17:58:53 +08:00
|
|
|
if (req->Page != 0) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "failure: requested page is zero\n");
|
2008-08-03 17:58:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2009-11-03 08:16:12 +08:00
|
|
|
s->win[wh].card_start = req->CardOffset;
|
|
|
|
if (s->ops->set_mem_map(s, &s->win[wh]) != 0) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "failed to set_mem_map\n");
|
2008-08-03 18:10:53 +08:00
|
|
|
return -EIO;
|
|
|
|
}
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_map_mem_page */
|
|
|
|
EXPORT_SYMBOL(pcmcia_map_mem_page);
|
|
|
|
|
|
|
|
|
|
|
|
/** pcmcia_modify_configuration
|
|
|
|
*
|
|
|
|
* Modify a locked socket configuration
|
|
|
|
*/
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_modify_configuration(struct pcmcia_device *p_dev,
|
2005-06-28 07:28:53 +08:00
|
|
|
modconf_t *mod)
|
|
|
|
{
|
|
|
|
struct pcmcia_socket *s;
|
|
|
|
config_t *c;
|
|
|
|
|
2005-07-08 08:59:02 +08:00
|
|
|
s = p_dev->socket;
|
2006-01-11 03:41:27 +08:00
|
|
|
c = p_dev->function_config;
|
|
|
|
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
|
|
|
dev_dbg(&s->dev, "No card present\n");
|
2008-08-03 17:10:56 +08:00
|
|
|
return -ENODEV;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
|
|
|
if (!(c->state & CONFIG_LOCKED)) {
|
|
|
|
dev_dbg(&s->dev, "Configuration isnt't locked\n");
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
if (mod->Attributes & CONF_IRQ_CHANGE_VALID) {
|
|
|
|
if (mod->Attributes & CONF_ENABLE_IRQ) {
|
|
|
|
c->Attributes |= CONF_ENABLE_IRQ;
|
|
|
|
s->socket.io_irq = s->irq.AssignedIRQ;
|
|
|
|
} else {
|
|
|
|
c->Attributes &= ~CONF_ENABLE_IRQ;
|
|
|
|
s->socket.io_irq = 0;
|
|
|
|
}
|
|
|
|
s->ops->set_socket(s, &s->socket);
|
|
|
|
}
|
|
|
|
|
2008-08-03 16:56:13 +08:00
|
|
|
if (mod->Attributes & CONF_VCC_CHANGE_VALID) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "changing Vcc is not allowed at this time\n");
|
2008-08-03 16:56:13 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* We only allow changing Vpp1 and Vpp2 to the same value */
|
|
|
|
if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) &&
|
|
|
|
(mod->Attributes & CONF_VPP2_CHANGE_VALID)) {
|
2008-10-30 19:02:54 +08:00
|
|
|
if (mod->Vpp1 != mod->Vpp2) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "Vpp1 and Vpp2 must be the same\n");
|
2008-08-03 16:56:13 +08:00
|
|
|
return -EINVAL;
|
2008-10-30 19:02:54 +08:00
|
|
|
}
|
2005-09-10 04:03:27 +08:00
|
|
|
s->socket.Vpp = mod->Vpp1;
|
2008-08-03 16:56:13 +08:00
|
|
|
if (s->ops->set_socket(s, &s->socket)) {
|
|
|
|
dev_printk(KERN_WARNING, &s->dev,
|
|
|
|
"Unable to set VPP\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
} else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) ||
|
2008-08-03 16:56:13 +08:00
|
|
|
(mod->Attributes & CONF_VPP2_CHANGE_VALID)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "changing Vcc is not allowed at this time\n");
|
2008-08-03 16:56:13 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-01-15 18:18:12 +08:00
|
|
|
if (mod->Attributes & CONF_IO_CHANGE_WIDTH) {
|
|
|
|
pccard_io_map io_off = { 0, 0, 0, 0, 1 };
|
|
|
|
pccard_io_map io_on;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
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 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* modify_configuration */
|
|
|
|
EXPORT_SYMBOL(pcmcia_modify_configuration);
|
|
|
|
|
|
|
|
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_release_configuration(struct pcmcia_device *p_dev)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
|
|
|
pccard_io_map io = { 0, 0, 0, 0, 1 };
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2006-01-15 16:32:39 +08:00
|
|
|
config_t *c = p_dev->function_config;
|
2005-06-28 07:28:53 +08:00
|
|
|
int i;
|
|
|
|
|
2006-03-02 07:09:29 +08:00
|
|
|
if (p_dev->_locked) {
|
|
|
|
p_dev->_locked = 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (--(s->lock_count) == 0) {
|
|
|
|
s->socket.flags = SS_OUTPUT_ENA; /* Is this correct? */
|
|
|
|
s->socket.Vpp = 0;
|
|
|
|
s->socket.io_irq = 0;
|
|
|
|
s->ops->set_socket(s, &s->socket);
|
|
|
|
}
|
2006-01-15 16:32:39 +08:00
|
|
|
}
|
|
|
|
if (c->state & CONFIG_LOCKED) {
|
|
|
|
c->state &= ~CONFIG_LOCKED;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (c->state & CONFIG_IO_REQ)
|
|
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
2006-01-15 15:04:43 +08:00
|
|
|
if (!s->io[i].res)
|
2005-06-28 07:28:53 +08:00
|
|
|
continue;
|
|
|
|
s->io[i].Config--;
|
|
|
|
if (s->io[i].Config != 0)
|
|
|
|
continue;
|
|
|
|
io.map = i;
|
|
|
|
s->ops->set_io_map(s, &io);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_release_configuration */
|
|
|
|
|
|
|
|
|
|
|
|
/** pcmcia_release_io
|
|
|
|
*
|
|
|
|
* Release_io() releases the I/O ranges allocated by a client. 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.
|
|
|
|
*/
|
2006-01-19 15:53:13 +08:00
|
|
|
static int pcmcia_release_io(struct pcmcia_device *p_dev, io_req_t *req)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2006-01-15 16:32:39 +08:00
|
|
|
config_t *c = p_dev->function_config;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-03-02 07:09:29 +08:00
|
|
|
if (!p_dev->_io )
|
2008-08-03 17:18:00 +08:00
|
|
|
return -EINVAL;
|
2006-01-15 16:32:39 +08:00
|
|
|
|
2006-03-02 07:09:29 +08:00
|
|
|
p_dev->_io = 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-01-15 16:32:39 +08:00
|
|
|
if ((c->io.BasePort1 != req->BasePort1) ||
|
|
|
|
(c->io.NumPorts1 != req->NumPorts1) ||
|
|
|
|
(c->io.BasePort2 != req->BasePort2) ||
|
|
|
|
(c->io.NumPorts2 != req->NumPorts2))
|
2008-08-03 18:15:11 +08:00
|
|
|
return -EINVAL;
|
2006-01-15 16:32:39 +08:00
|
|
|
|
|
|
|
c->state &= ~CONFIG_IO_REQ;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
release_io_space(s, req->BasePort1, req->NumPorts1);
|
|
|
|
if (req->NumPorts2)
|
|
|
|
release_io_space(s, req->BasePort2, req->NumPorts2);
|
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_release_io */
|
|
|
|
|
|
|
|
|
2006-01-19 15:53:13 +08:00
|
|
|
static int pcmcia_release_irq(struct pcmcia_device *p_dev, irq_req_t *req)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2006-01-15 16:32:39 +08:00
|
|
|
config_t *c= p_dev->function_config;
|
|
|
|
|
2006-03-02 07:09:29 +08:00
|
|
|
if (!p_dev->_irq)
|
2008-08-03 17:18:00 +08:00
|
|
|
return -EINVAL;
|
2006-03-02 07:09:29 +08:00
|
|
|
p_dev->_irq = 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-01-15 16:32:39 +08:00
|
|
|
if (c->state & CONFIG_LOCKED)
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2008-08-03 17:58:53 +08:00
|
|
|
if (c->irq.Attributes != req->Attributes) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "IRQ attributes must match assigned ones\n");
|
2008-08-03 17:58:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2008-08-03 18:10:53 +08:00
|
|
|
if (s->irq.AssignedIRQ != req->AssignedIRQ) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "IRQ must match assigned one\n");
|
2008-08-03 18:10:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2006-01-15 16:32:39 +08:00
|
|
|
if (--s->irq.Config == 0) {
|
|
|
|
c->state &= ~CONFIG_IRQ_REQ;
|
|
|
|
s->irq.AssignedIRQ = 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (req->Attributes & IRQ_HANDLE_PRESENT) {
|
|
|
|
free_irq(req->AssignedIRQ, req->Instance);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
|
|
|
pcmcia_used_irq[req->AssignedIRQ]--;
|
|
|
|
#endif
|
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_release_irq */
|
|
|
|
|
|
|
|
|
2006-12-13 18:46:38 +08:00
|
|
|
int pcmcia_release_window(struct pcmcia_device *p_dev, window_handle_t wh)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2006-12-13 18:46:53 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2009-11-03 08:16:12 +08:00
|
|
|
pccard_mem_map *win;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-12-13 18:46:53 +08:00
|
|
|
wh--;
|
|
|
|
if (wh >= MAX_WIN)
|
2008-08-03 17:18:00 +08:00
|
|
|
return -EINVAL;
|
2006-12-13 18:46:53 +08:00
|
|
|
|
|
|
|
win = &s->win[wh];
|
|
|
|
|
|
|
|
if (!(p_dev->_win & CLIENT_WIN_REQ(wh))) {
|
2009-10-24 18:20:18 +08:00
|
|
|
dev_dbg(&s->dev, "not releasing unknown window\n");
|
2008-08-03 17:18:00 +08:00
|
|
|
return -EINVAL;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Shut down memory window */
|
2009-11-03 08:16:12 +08:00
|
|
|
win->flags &= ~MAP_ACTIVE;
|
|
|
|
s->ops->set_mem_map(s, win);
|
2006-12-13 18:46:53 +08:00
|
|
|
s->state &= ~SOCKET_WIN_REQ(wh);
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Release system memory */
|
2009-11-03 08:16:12 +08:00
|
|
|
if (win->res) {
|
|
|
|
release_resource(win->res);
|
|
|
|
kfree(win->res);
|
|
|
|
win->res = NULL;
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
2006-12-13 18:46:53 +08:00
|
|
|
p_dev->_win &= ~CLIENT_WIN_REQ(wh);
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_release_window */
|
|
|
|
EXPORT_SYMBOL(pcmcia_release_window);
|
|
|
|
|
|
|
|
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_request_configuration(struct pcmcia_device *p_dev,
|
2005-06-28 07:28:53 +08:00
|
|
|
config_req_t *req)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u_int base;
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2005-06-28 07:28:53 +08:00
|
|
|
config_t *c;
|
|
|
|
pccard_io_map iomap;
|
|
|
|
|
|
|
|
if (!(s->state & SOCKET_PRESENT))
|
2009-06-29 00:26:41 +08:00
|
|
|
return -ENODEV;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2008-08-03 16:47:59 +08:00
|
|
|
if (req->IntType & INT_CARDBUS) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "IntType may not be INT_CARDBUS\n");
|
2008-08-03 16:47:59 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2006-01-11 03:41:27 +08:00
|
|
|
c = p_dev->function_config;
|
2009-10-24 18:20:18 +08:00
|
|
|
if (c->state & CONFIG_LOCKED) {
|
|
|
|
dev_dbg(&s->dev, "Configuration is locked\n");
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Do power control. We don't allow changes in Vcc. */
|
2006-01-15 19:43:16 +08:00
|
|
|
s->socket.Vpp = req->Vpp;
|
2008-08-03 16:56:13 +08:00
|
|
|
if (s->ops->set_socket(s, &s->socket)) {
|
|
|
|
dev_printk(KERN_WARNING, &s->dev,
|
|
|
|
"Unable to set socket state\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Pick memory or I/O card, DMA mode, interrupt */
|
|
|
|
c->IntType = req->IntType;
|
|
|
|
c->Attributes = req->Attributes;
|
|
|
|
if (req->IntType & INT_MEMORY_AND_IO)
|
|
|
|
s->socket.flags |= SS_IOCARD;
|
|
|
|
if (req->IntType & INT_ZOOMED_VIDEO)
|
|
|
|
s->socket.flags |= SS_ZVCARD | SS_IOCARD;
|
|
|
|
if (req->Attributes & CONF_ENABLE_DMA)
|
|
|
|
s->socket.flags |= SS_DMA_MODE;
|
|
|
|
if (req->Attributes & CONF_ENABLE_SPKR)
|
|
|
|
s->socket.flags |= SS_SPKR_ENA;
|
|
|
|
if (req->Attributes & CONF_ENABLE_IRQ)
|
|
|
|
s->socket.io_irq = s->irq.AssignedIRQ;
|
|
|
|
else
|
|
|
|
s->socket.io_irq = 0;
|
|
|
|
s->ops->set_socket(s, &s->socket);
|
|
|
|
s->lock_count++;
|
|
|
|
|
|
|
|
/* Set up CIS configuration registers */
|
|
|
|
base = c->ConfigBase = req->ConfigBase;
|
2006-01-11 03:40:40 +08:00
|
|
|
c->CardValues = req->Present;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Present & PRESENT_COPY) {
|
|
|
|
c->Copy = req->Copy;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_SCR)>>1, 1, &c->Copy);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_OPTION) {
|
|
|
|
if (s->functions == 1) {
|
|
|
|
c->Option = req->ConfigIndex & COR_CONFIG_MASK;
|
|
|
|
} else {
|
|
|
|
c->Option = req->ConfigIndex & COR_MFC_CONFIG_MASK;
|
|
|
|
c->Option |= COR_FUNC_ENA|COR_IREQ_ENA;
|
|
|
|
if (req->Present & PRESENT_IOBASE_0)
|
|
|
|
c->Option |= COR_ADDR_DECODE;
|
|
|
|
}
|
|
|
|
if (c->state & CONFIG_IRQ_REQ)
|
|
|
|
if (!(c->irq.Attributes & IRQ_FORCED_PULSE))
|
|
|
|
c->Option |= COR_LEVEL_REQ;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_COR)>>1, 1, &c->Option);
|
|
|
|
mdelay(40);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_STATUS) {
|
|
|
|
c->Status = req->Status;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_CCSR)>>1, 1, &c->Status);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_PIN_REPLACE) {
|
|
|
|
c->Pin = req->Pin;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_PRR)>>1, 1, &c->Pin);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_EXT_STATUS) {
|
|
|
|
c->ExtStatus = req->ExtStatus;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_ESR)>>1, 1, &c->ExtStatus);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_IOBASE_0) {
|
|
|
|
u_char b = c->io.BasePort1 & 0xff;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_0)>>1, 1, &b);
|
|
|
|
b = (c->io.BasePort1 >> 8) & 0xff;
|
|
|
|
pcmcia_write_cis_mem(s, 1, (base + CISREG_IOBASE_1)>>1, 1, &b);
|
|
|
|
}
|
|
|
|
if (req->Present & PRESENT_IOSIZE) {
|
|
|
|
u_char b = c->io.NumPorts1 + c->io.NumPorts2 - 1;
|
|
|
|
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 15:04:43 +08:00
|
|
|
if (s->io[i].res) {
|
2005-06-28 07:28:53 +08:00
|
|
|
iomap.map = i;
|
|
|
|
iomap.flags = MAP_ACTIVE;
|
2006-01-15 15:04:43 +08:00
|
|
|
switch (s->io[i].res->flags & IO_DATA_PATH_WIDTH) {
|
2005-06-28 07:28:53 +08: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 15:04:43 +08:00
|
|
|
iomap.start = s->io[i].res->start;
|
|
|
|
iomap.stop = s->io[i].res->end;
|
2005-06-28 07:28:53 +08:00
|
|
|
s->ops->set_io_map(s, &iomap);
|
|
|
|
s->io[i].Config++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c->state |= CONFIG_LOCKED;
|
2006-03-02 07:09:29 +08:00
|
|
|
p_dev->_locked = 1;
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_request_configuration */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_configuration);
|
|
|
|
|
|
|
|
|
|
|
|
/** pcmcia_request_io
|
|
|
|
*
|
|
|
|
* Request_io() reserves ranges of port addresses for a socket.
|
|
|
|
* I have not implemented range sharing or alias addressing.
|
|
|
|
*/
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_request_io(struct pcmcia_device *p_dev, io_req_t *req)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2005-06-28 07:28:53 +08:00
|
|
|
config_t *c;
|
|
|
|
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
|
|
|
dev_dbg(&s->dev, "No card present\n");
|
2008-08-03 17:10:56 +08:00
|
|
|
return -ENODEV;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
if (!req)
|
2008-08-03 16:47:59 +08:00
|
|
|
return -EINVAL;
|
2006-01-11 03:41:27 +08:00
|
|
|
c = p_dev->function_config;
|
2009-10-24 18:20:18 +08:00
|
|
|
if (c->state & CONFIG_LOCKED) {
|
|
|
|
dev_dbg(&s->dev, "Configuration is locked\n");
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2008-08-03 17:40:19 +08:00
|
|
|
if (c->state & CONFIG_IO_REQ) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "IO already configured\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EBUSY;
|
|
|
|
}
|
2008-08-03 17:58:53 +08:00
|
|
|
if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "bad attribute setting for IO region 1\n");
|
2008-08-03 17:58:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
if ((req->NumPorts2 > 0) &&
|
2008-08-03 17:58:53 +08:00
|
|
|
(req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "bad attribute setting for IO region 2\n");
|
2008-08-03 17:58:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "trying to allocate resource 1\n");
|
2005-06-28 07:28:53 +08:00
|
|
|
if (alloc_io_space(s, req->Attributes1, &req->BasePort1,
|
2008-08-03 17:40:19 +08:00
|
|
|
req->NumPorts1, req->IOAddrLines)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "allocation of resource 1 failed\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EBUSY;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
if (req->NumPorts2) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "trying to allocate resource 2\n");
|
2005-06-28 07:28:53 +08:00
|
|
|
if (alloc_io_space(s, req->Attributes2, &req->BasePort2,
|
|
|
|
req->NumPorts2, req->IOAddrLines)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "allocation of resource 2 failed\n");
|
2005-06-28 07:28:53 +08:00
|
|
|
release_io_space(s, req->BasePort1, req->NumPorts1);
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EBUSY;
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c->io = *req;
|
|
|
|
c->state |= CONFIG_IO_REQ;
|
2006-03-02 07:09:29 +08:00
|
|
|
p_dev->_io = 1;
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_request_io */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_io);
|
|
|
|
|
|
|
|
|
|
|
|
/** pcmcia_request_irq
|
|
|
|
*
|
|
|
|
* Request_irq() reserves an irq for this client.
|
|
|
|
*
|
|
|
|
* Also, since Linux only reserves irq's when they are actually
|
|
|
|
* hooked, we don't guarantee that an irq will still be available
|
|
|
|
* when the configuration is locked. Now that I think about it,
|
|
|
|
* there might be a way to fix this using a dummy handler.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t test_action(int cpl, void *dev_id)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_request_irq(struct pcmcia_device *p_dev, irq_req_t *req)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = p_dev->socket;
|
2005-06-28 07:28:53 +08:00
|
|
|
config_t *c;
|
2008-08-03 17:40:19 +08:00
|
|
|
int ret = -EINVAL, irq = 0;
|
2006-05-16 23:16:44 +08:00
|
|
|
int type;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
|
|
|
dev_dbg(&s->dev, "No card present\n");
|
2008-08-03 17:10:56 +08:00
|
|
|
return -ENODEV;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2006-01-11 03:41:27 +08:00
|
|
|
c = p_dev->function_config;
|
2009-10-24 18:20:18 +08:00
|
|
|
if (c->state & CONFIG_LOCKED) {
|
|
|
|
dev_dbg(&s->dev, "Configuration is locked\n");
|
2008-08-03 17:23:42 +08:00
|
|
|
return -EACCES;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2008-08-03 17:40:19 +08:00
|
|
|
if (c->state & CONFIG_IRQ_REQ) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "IRQ already configured\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EBUSY;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2006-05-16 23:16:44 +08:00
|
|
|
/* Decide what type of interrupt we are registering */
|
|
|
|
type = 0;
|
|
|
|
if (s->functions > 1) /* All of this ought to be handled higher up */
|
2006-07-02 10:29:38 +08:00
|
|
|
type = IRQF_SHARED;
|
2008-10-06 00:35:59 +08:00
|
|
|
else if (req->Attributes & IRQ_TYPE_DYNAMIC_SHARING)
|
2006-07-02 10:29:38 +08:00
|
|
|
type = IRQF_SHARED;
|
2008-10-06 00:35:59 +08:00
|
|
|
else printk(KERN_WARNING "pcmcia: Driver needs updating to support IRQ sharing.\n");
|
2006-05-16 23:16:44 +08:00
|
|
|
|
2005-06-28 07:28:53 +08:00
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
2008-06-16 20:35:15 +08:00
|
|
|
|
|
|
|
#ifdef IRQ_NOAUTOEN
|
|
|
|
/* if the underlying IRQ infrastructure allows for it, only allocate
|
|
|
|
* the IRQ, but do not enable it
|
|
|
|
*/
|
|
|
|
if (!(req->Attributes & IRQ_HANDLE_PRESENT))
|
|
|
|
type |= IRQ_NOAUTOEN;
|
|
|
|
#endif /* IRQ_NOAUTOEN */
|
|
|
|
|
2005-06-28 07:28:53 +08:00
|
|
|
if (s->irq.AssignedIRQ != 0) {
|
|
|
|
/* If the interrupt is already assigned, it must be the same */
|
|
|
|
irq = s->irq.AssignedIRQ;
|
|
|
|
} else {
|
|
|
|
int try;
|
|
|
|
u32 mask = s->irq_mask;
|
2005-07-28 16:07:27 +08:00
|
|
|
void *data = &p_dev->dev.driver; /* something unique to this device */
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
for (try = 0; try < 64; try++) {
|
|
|
|
irq = try % 32;
|
|
|
|
|
|
|
|
/* marked as available by driver, and not blocked by userspace? */
|
|
|
|
if (!((mask >> irq) & 1))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* avoid an IRQ which is already used by a PCMCIA card */
|
|
|
|
if ((try < 32) && pcmcia_used_irq[irq])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* register the correct driver, if possible, of 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,
|
|
|
|
(req->Attributes & IRQ_HANDLE_PRESENT) ? req->Handler : test_action,
|
2006-05-16 23:16:44 +08:00
|
|
|
type,
|
2005-09-10 04:03:29 +08:00
|
|
|
p_dev->devname,
|
2005-06-28 07:28:53 +08:00
|
|
|
(req->Attributes & IRQ_HANDLE_PRESENT) ? req->Instance : data);
|
|
|
|
if (!ret) {
|
|
|
|
if (!(req->Attributes & IRQ_HANDLE_PRESENT))
|
|
|
|
free_irq(irq, data);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
2005-09-10 04:03:25 +08:00
|
|
|
/* only assign PCI irq if no IRQ already assigned */
|
|
|
|
if (ret && !s->irq.AssignedIRQ) {
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!s->pci_irq) {
|
|
|
|
dev_printk(KERN_INFO, &s->dev, "no IRQ found\n");
|
2005-06-28 07:28:53 +08:00
|
|
|
return ret;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2006-07-02 10:29:38 +08:00
|
|
|
type = IRQF_SHARED;
|
2005-06-28 07:28:53 +08:00
|
|
|
irq = s->pci_irq;
|
|
|
|
}
|
|
|
|
|
2006-05-16 23:16:44 +08:00
|
|
|
if (ret && (req->Attributes & IRQ_HANDLE_PRESENT)) {
|
2008-08-03 17:40:19 +08:00
|
|
|
ret = request_irq(irq, req->Handler, type,
|
|
|
|
p_dev->devname, req->Instance);
|
2009-10-24 18:20:18 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_printk(KERN_INFO, &s->dev,
|
|
|
|
"request_irq() failed\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return ret;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
|
|
|
|
2006-05-16 23:16:44 +08:00
|
|
|
/* Make sure the fact the request type was overridden is passed back */
|
2006-07-02 10:29:38 +08:00
|
|
|
if (type == IRQF_SHARED && !(req->Attributes & IRQ_TYPE_DYNAMIC_SHARING)) {
|
2006-05-16 23:16:44 +08:00
|
|
|
req->Attributes |= IRQ_TYPE_DYNAMIC_SHARING;
|
2008-08-03 00:33:56 +08:00
|
|
|
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");
|
2006-05-16 23:16:44 +08:00
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
c->irq.Attributes = req->Attributes;
|
|
|
|
s->irq.AssignedIRQ = req->AssignedIRQ = irq;
|
|
|
|
s->irq.Config++;
|
|
|
|
|
|
|
|
c->state |= CONFIG_IRQ_REQ;
|
2006-03-02 07:09:29 +08:00
|
|
|
p_dev->_irq = 1;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_PCMCIA_PROBE
|
|
|
|
pcmcia_used_irq[irq]++;
|
|
|
|
#endif
|
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_request_irq */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_irq);
|
|
|
|
|
|
|
|
|
|
|
|
/** pcmcia_request_window
|
|
|
|
*
|
|
|
|
* Request_window() establishes a mapping between card memory space
|
|
|
|
* and system memory space.
|
|
|
|
*/
|
2005-07-08 08:59:02 +08:00
|
|
|
int pcmcia_request_window(struct pcmcia_device **p_dev, win_req_t *req, window_handle_t *wh)
|
2005-06-28 07:28:53 +08:00
|
|
|
{
|
2005-07-08 08:59:02 +08:00
|
|
|
struct pcmcia_socket *s = (*p_dev)->socket;
|
2009-11-03 08:16:12 +08:00
|
|
|
pccard_mem_map *win;
|
2005-06-28 07:28:53 +08:00
|
|
|
u_long align;
|
|
|
|
int w;
|
|
|
|
|
2009-10-24 18:20:18 +08:00
|
|
|
if (!(s->state & SOCKET_PRESENT)) {
|
|
|
|
dev_dbg(&s->dev, "No card present\n");
|
2008-08-03 17:10:56 +08:00
|
|
|
return -ENODEV;
|
2009-10-24 18:20:18 +08:00
|
|
|
}
|
2008-08-03 17:58:53 +08:00
|
|
|
if (req->Attributes & (WIN_PAGED | WIN_SHARED)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "bad attribute setting for iomem region\n");
|
2008-08-03 17:58:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Window size defaults to smallest available */
|
|
|
|
if (req->Size == 0)
|
|
|
|
req->Size = s->map_size;
|
|
|
|
align = (((s->features & SS_CAP_MEM_ALIGN) ||
|
|
|
|
(req->Attributes & WIN_STRICT_ALIGN)) ?
|
|
|
|
req->Size : s->map_size);
|
2008-08-03 18:10:53 +08:00
|
|
|
if (req->Size & (s->map_size-1)) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "invalid map size\n");
|
2008-08-03 18:10:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
if ((req->Base && (s->features & SS_CAP_STATIC_MAP)) ||
|
2008-08-03 18:10:53 +08:00
|
|
|
(req->Base & (align-1))) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "invalid base address\n");
|
2008-08-03 18:10:53 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Base)
|
|
|
|
align = 0;
|
|
|
|
|
|
|
|
/* Allocate system memory window */
|
|
|
|
for (w = 0; w < MAX_WIN; w++)
|
|
|
|
if (!(s->state & SOCKET_WIN_REQ(w))) break;
|
2008-08-03 17:40:19 +08:00
|
|
|
if (w == MAX_WIN) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "all windows are used already\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
win = &s->win[w];
|
|
|
|
|
|
|
|
if (!(s->features & SS_CAP_STATIC_MAP)) {
|
2009-11-03 08:16:12 +08:00
|
|
|
win->res = pcmcia_find_mem_region(req->Base, req->Size, align,
|
2005-06-28 07:28:53 +08:00
|
|
|
(req->Attributes & WIN_MAP_BELOW_1MB), s);
|
2009-11-03 08:16:12 +08:00
|
|
|
if (!win->res) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "allocating mem region failed\n");
|
2008-08-03 17:40:19 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
2006-03-02 07:09:29 +08:00
|
|
|
(*p_dev)->_win |= CLIENT_WIN_REQ(w);
|
2005-06-28 07:28:53 +08:00
|
|
|
|
|
|
|
/* Configure the socket controller */
|
2009-11-03 08:16:12 +08:00
|
|
|
win->map = w+1;
|
|
|
|
win->flags = 0;
|
|
|
|
win->speed = req->AccessSpeed;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Attributes & WIN_MEMORY_TYPE)
|
2009-11-03 08:16:12 +08:00
|
|
|
win->flags |= MAP_ATTRIB;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Attributes & WIN_ENABLE)
|
2009-11-03 08:16:12 +08:00
|
|
|
win->flags |= MAP_ACTIVE;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Attributes & WIN_DATA_WIDTH_16)
|
2009-11-03 08:16:12 +08:00
|
|
|
win->flags |= MAP_16BIT;
|
2005-06-28 07:28:53 +08:00
|
|
|
if (req->Attributes & WIN_USE_WAIT)
|
2009-11-03 08:16:12 +08:00
|
|
|
win->flags |= MAP_USE_WAIT;
|
|
|
|
win->card_start = 0;
|
|
|
|
if (s->ops->set_mem_map(s, win) != 0) {
|
2009-10-23 18:51:28 +08:00
|
|
|
dev_dbg(&s->dev, "failed to set memory mapping\n");
|
2008-08-03 18:15:11 +08:00
|
|
|
return -EIO;
|
|
|
|
}
|
2005-06-28 07:28:53 +08:00
|
|
|
s->state |= SOCKET_WIN_REQ(w);
|
|
|
|
|
|
|
|
/* Return window handle */
|
|
|
|
if (s->features & SS_CAP_STATIC_MAP) {
|
2009-11-03 08:16:12 +08:00
|
|
|
req->Base = win->static_start;
|
2005-06-28 07:28:53 +08:00
|
|
|
} else {
|
2009-11-03 08:16:12 +08:00
|
|
|
req->Base = win->res->start;
|
2005-06-28 07:28:53 +08:00
|
|
|
}
|
2006-12-13 18:46:53 +08:00
|
|
|
*wh = w + 1;
|
2005-06-28 07:28:53 +08:00
|
|
|
|
2008-08-03 16:07:45 +08:00
|
|
|
return 0;
|
2005-06-28 07:28:53 +08:00
|
|
|
} /* pcmcia_request_window */
|
|
|
|
EXPORT_SYMBOL(pcmcia_request_window);
|
2006-01-15 16:32:39 +08:00
|
|
|
|
|
|
|
void pcmcia_disable_device(struct pcmcia_device *p_dev) {
|
|
|
|
pcmcia_release_configuration(p_dev);
|
2006-03-05 17:45:09 +08:00
|
|
|
pcmcia_release_io(p_dev, &p_dev->io);
|
|
|
|
pcmcia_release_irq(p_dev, &p_dev->irq);
|
2008-06-15 00:51:06 +08:00
|
|
|
if (p_dev->win)
|
2006-12-13 18:46:38 +08:00
|
|
|
pcmcia_release_window(p_dev, p_dev->win);
|
2006-01-15 16:32:39 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_disable_device);
|
2008-07-29 14:38:30 +08:00
|
|
|
|
|
|
|
|
|
|
|
struct pcmcia_cfg_mem {
|
2009-10-19 05:32:33 +08:00
|
|
|
struct pcmcia_device *p_dev;
|
|
|
|
void *priv_data;
|
|
|
|
int (*conf_check) (struct pcmcia_device *p_dev,
|
|
|
|
cistpl_cftable_entry_t *cfg,
|
|
|
|
cistpl_cftable_entry_t *dflt,
|
|
|
|
unsigned int vcc,
|
|
|
|
void *priv_data);
|
2008-07-29 14:38:30 +08:00
|
|
|
cisparse_t parse;
|
2008-08-02 21:30:31 +08:00
|
|
|
cistpl_cftable_entry_t dflt;
|
2008-07-29 14:38:30 +08:00
|
|
|
};
|
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
/**
|
|
|
|
* pcmcia_do_loop_config() - internal helper for pcmcia_loop_config()
|
|
|
|
*
|
|
|
|
* pcmcia_do_loop_config() is the internal callback for the call from
|
|
|
|
* pcmcia_loop_config() to pccard_loop_tuple(). Data is transferred
|
|
|
|
* by a struct pcmcia_cfg_mem.
|
|
|
|
*/
|
|
|
|
static int pcmcia_do_loop_config(tuple_t *tuple, cisparse_t *parse, void *priv)
|
|
|
|
{
|
|
|
|
cistpl_cftable_entry_t *cfg = &parse->cftable_entry;
|
|
|
|
struct pcmcia_cfg_mem *cfg_mem = priv;
|
|
|
|
|
|
|
|
/* default values */
|
|
|
|
cfg_mem->p_dev->conf.ConfigIndex = cfg->index;
|
|
|
|
if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
|
|
|
|
cfg_mem->dflt = *cfg;
|
|
|
|
|
|
|
|
return cfg_mem->conf_check(cfg_mem->p_dev, cfg, &cfg_mem->dflt,
|
|
|
|
cfg_mem->p_dev->socket->socket.Vcc,
|
|
|
|
cfg_mem->priv_data);
|
|
|
|
}
|
|
|
|
|
2008-07-29 14:38:30 +08:00
|
|
|
/**
|
|
|
|
* pcmcia_loop_config() - loop over configuration options
|
|
|
|
* @p_dev: the struct pcmcia_device which we need to loop for.
|
|
|
|
* @conf_check: function to call for each configuration option.
|
|
|
|
* It gets passed the struct pcmcia_device, the CIS data
|
|
|
|
* describing the configuration option, and private data
|
|
|
|
* being passed to pcmcia_loop_config()
|
|
|
|
* @priv_data: private data to be passed to the conf_check function.
|
|
|
|
*
|
|
|
|
* pcmcia_loop_config() loops over all configuration options, and calls
|
|
|
|
* the driver-specific conf_check() for each one, checking whether
|
2009-07-29 20:25:37 +08:00
|
|
|
* it is a valid one. Returns 0 on success or errorcode otherwise.
|
2008-07-29 14:38:30 +08:00
|
|
|
*/
|
|
|
|
int pcmcia_loop_config(struct pcmcia_device *p_dev,
|
|
|
|
int (*conf_check) (struct pcmcia_device *p_dev,
|
|
|
|
cistpl_cftable_entry_t *cfg,
|
2008-08-02 21:30:31 +08:00
|
|
|
cistpl_cftable_entry_t *dflt,
|
2008-08-02 22:12:00 +08:00
|
|
|
unsigned int vcc,
|
2008-07-29 14:38:30 +08:00
|
|
|
void *priv_data),
|
|
|
|
void *priv_data)
|
|
|
|
{
|
|
|
|
struct pcmcia_cfg_mem *cfg_mem;
|
2009-07-29 20:25:37 +08:00
|
|
|
int ret;
|
2008-07-29 14:38:30 +08:00
|
|
|
|
|
|
|
cfg_mem = kzalloc(sizeof(struct pcmcia_cfg_mem), GFP_KERNEL);
|
|
|
|
if (cfg_mem == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
cfg_mem->p_dev = p_dev;
|
|
|
|
cfg_mem->conf_check = conf_check;
|
|
|
|
cfg_mem->priv_data = priv_data;
|
2008-08-02 22:12:00 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
ret = pccard_loop_tuple(p_dev->socket, p_dev->func,
|
|
|
|
CISTPL_CFTABLE_ENTRY, &cfg_mem->parse,
|
|
|
|
cfg_mem, pcmcia_do_loop_config);
|
2008-07-29 14:38:30 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
kfree(cfg_mem);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(pcmcia_loop_config);
|
2008-08-02 20:59:13 +08:00
|
|
|
|
2008-07-29 14:38:30 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
struct pcmcia_loop_mem {
|
|
|
|
struct pcmcia_device *p_dev;
|
|
|
|
void *priv_data;
|
|
|
|
int (*loop_tuple) (struct pcmcia_device *p_dev,
|
|
|
|
tuple_t *tuple,
|
|
|
|
void *priv_data);
|
|
|
|
};
|
2008-07-29 14:38:30 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
/**
|
|
|
|
* pcmcia_do_loop_tuple() - internal helper for pcmcia_loop_config()
|
|
|
|
*
|
|
|
|
* pcmcia_do_loop_tuple() is the internal callback for the call from
|
|
|
|
* pcmcia_loop_tuple() to pccard_loop_tuple(). Data is transferred
|
|
|
|
* by a struct pcmcia_cfg_mem.
|
|
|
|
*/
|
|
|
|
static int pcmcia_do_loop_tuple(tuple_t *tuple, cisparse_t *parse, void *priv)
|
|
|
|
{
|
|
|
|
struct pcmcia_loop_mem *loop = priv;
|
2008-08-02 20:59:13 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
return loop->loop_tuple(loop->p_dev, tuple, loop->priv_data);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_loop_tuple() - loop over tuples in the CIS
|
|
|
|
* @p_dev: the struct pcmcia_device which we need to loop for.
|
|
|
|
* @code: which CIS code shall we look for?
|
|
|
|
* @priv_data: private data to be passed to the loop_tuple function.
|
|
|
|
* @loop_tuple: function to call for each CIS entry of type @function. IT
|
|
|
|
* gets passed the raw tuple and @priv_data.
|
|
|
|
*
|
|
|
|
* pcmcia_loop_tuple() loops over all CIS entries of type @function, and
|
|
|
|
* calls the @loop_tuple function for each entry. If the call to @loop_tuple
|
|
|
|
* returns 0, the loop exits. Returns 0 on success or errorcode otherwise.
|
|
|
|
*/
|
|
|
|
int pcmcia_loop_tuple(struct pcmcia_device *p_dev, cisdata_t code,
|
|
|
|
int (*loop_tuple) (struct pcmcia_device *p_dev,
|
|
|
|
tuple_t *tuple,
|
|
|
|
void *priv_data),
|
|
|
|
void *priv_data)
|
|
|
|
{
|
|
|
|
struct pcmcia_loop_mem loop = {
|
|
|
|
.p_dev = p_dev,
|
|
|
|
.loop_tuple = loop_tuple,
|
|
|
|
.priv_data = priv_data};
|
|
|
|
|
|
|
|
return pccard_loop_tuple(p_dev->socket, p_dev->func, code, NULL,
|
|
|
|
&loop, pcmcia_do_loop_tuple);
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL(pcmcia_loop_tuple);
|
2008-07-29 14:38:30 +08:00
|
|
|
|
2009-10-19 05:32:33 +08:00
|
|
|
|
|
|
|
struct pcmcia_loop_get {
|
|
|
|
size_t len;
|
|
|
|
cisdata_t **buf;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_do_get_tuple() - internal helper for pcmcia_get_tuple()
|
|
|
|
*
|
|
|
|
* pcmcia_do_get_tuple() is the internal callback for the call from
|
|
|
|
* pcmcia_get_tuple() to pcmcia_loop_tuple(). As we're only interested in
|
|
|
|
* the first tuple, return 0 unconditionally. Create a memory buffer large
|
|
|
|
* enough to hold the content of the tuple, and fill it with the tuple data.
|
|
|
|
* The caller is responsible to free the buffer.
|
|
|
|
*/
|
|
|
|
static int pcmcia_do_get_tuple(struct pcmcia_device *p_dev, tuple_t *tuple,
|
|
|
|
void *priv)
|
|
|
|
{
|
|
|
|
struct pcmcia_loop_get *get = priv;
|
|
|
|
|
|
|
|
*get->buf = kzalloc(tuple->TupleDataLen, GFP_KERNEL);
|
|
|
|
if (*get->buf) {
|
|
|
|
get->len = tuple->TupleDataLen;
|
|
|
|
memcpy(*get->buf, tuple->TupleData, tuple->TupleDataLen);
|
2009-10-24 18:20:18 +08:00
|
|
|
} else
|
|
|
|
dev_dbg(&p_dev->dev, "do_get_tuple: out of memory\n");
|
2009-10-19 05:32:33 +08:00
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_get_tuple() - get first tuple from CIS
|
|
|
|
* @p_dev: the struct pcmcia_device which we need to loop for.
|
|
|
|
* @code: which CIS code shall we look for?
|
|
|
|
* @buf: pointer to store the buffer to.
|
|
|
|
*
|
|
|
|
* pcmcia_get_tuple() gets the content of the first CIS entry of type @code.
|
|
|
|
* It returns the buffer length (or zero). The caller is responsible to free
|
|
|
|
* the buffer passed in @buf.
|
|
|
|
*/
|
|
|
|
size_t pcmcia_get_tuple(struct pcmcia_device *p_dev, cisdata_t code,
|
|
|
|
unsigned char **buf)
|
|
|
|
{
|
|
|
|
struct pcmcia_loop_get get = {
|
|
|
|
.len = 0,
|
|
|
|
.buf = buf,
|
|
|
|
};
|
|
|
|
|
|
|
|
*get.buf = NULL;
|
|
|
|
pcmcia_loop_tuple(p_dev, code, pcmcia_do_get_tuple, &get);
|
|
|
|
|
|
|
|
return get.len;
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL(pcmcia_get_tuple);
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_do_get_mac() - internal helper for pcmcia_get_mac_from_cis()
|
|
|
|
*
|
|
|
|
* pcmcia_do_get_mac() is the internal callback for the call from
|
|
|
|
* pcmcia_get_mac_from_cis() to pcmcia_loop_tuple(). We check whether the
|
|
|
|
* tuple contains a proper LAN_NODE_ID of length 6, and copy the data
|
|
|
|
* to struct net_device->dev_addr[i].
|
|
|
|
*/
|
|
|
|
static int pcmcia_do_get_mac(struct pcmcia_device *p_dev, tuple_t *tuple,
|
|
|
|
void *priv)
|
|
|
|
{
|
|
|
|
struct net_device *dev = priv;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (tuple->TupleData[0] != CISTPL_FUNCE_LAN_NODE_ID)
|
|
|
|
return -EINVAL;
|
|
|
|
if (tuple->TupleDataLen < ETH_ALEN + 2) {
|
|
|
|
dev_warn(&p_dev->dev, "Invalid CIS tuple length for "
|
|
|
|
"LAN_NODE_ID\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tuple->TupleData[1] != ETH_ALEN) {
|
|
|
|
dev_warn(&p_dev->dev, "Invalid header for LAN_NODE_ID\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
for (i = 0; i < 6; i++)
|
|
|
|
dev->dev_addr[i] = tuple->TupleData[i+2];
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* pcmcia_get_mac_from_cis() - read out MAC address from CISTPL_FUNCE
|
|
|
|
* @p_dev: the struct pcmcia_device for which we want the address.
|
|
|
|
* @dev: a properly prepared struct net_device to store the info to.
|
|
|
|
*
|
|
|
|
* pcmcia_get_mac_from_cis() reads out the hardware MAC address from
|
|
|
|
* CISTPL_FUNCE and stores it into struct net_device *dev->dev_addr which
|
|
|
|
* must be set up properly by the driver (see examples!).
|
|
|
|
*/
|
|
|
|
int pcmcia_get_mac_from_cis(struct pcmcia_device *p_dev, struct net_device *dev)
|
|
|
|
{
|
|
|
|
return pcmcia_loop_tuple(p_dev, CISTPL_FUNCE, pcmcia_do_get_mac, dev);
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL(pcmcia_get_mac_from_cis);
|
2008-07-29 14:38:30 +08:00
|
|
|
|