mirror of
https://github.com/torvalds/linux.git
synced 2024-11-27 06:31:52 +00:00
2243acd50a
The add_dev and remove_dev callbacks in struct class_interface currently pass in a pointer back to the class_interface structure that is calling them, but none of the callback implementations actually use this pointer as it is pointless (the structure is known, the driver passed it in in the first place if it is really needed again.) So clean this up and just remove the pointer from the callbacks and fix up all callback functions. Cc: Jean Delvare <jdelvare@suse.com> Cc: Guenter Roeck <linux@roeck-us.net> Cc: "David S. Miller" <davem@davemloft.net> Cc: Eric Dumazet <edumazet@google.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: Kurt Schwemmer <kurt.schwemmer@microsemi.com> Cc: Jon Mason <jdmason@kudzu.us> Cc: Dave Jiang <dave.jiang@intel.com> Cc: Allen Hubbe <allenbh@gmail.com> Cc: Dominik Brodowski <linux@dominikbrodowski.net> Cc: Matt Porter <mporter@kernel.crashing.org> Cc: Alexandre Bounine <alex.bou9@gmail.com> Cc: "James E.J. Bottomley" <jejb@linux.ibm.com> Cc: "Martin K. Petersen" <martin.petersen@oracle.com> Cc: Doug Gilbert <dgilbert@interlog.com> Cc: John Stultz <jstultz@google.com> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: Stephen Boyd <sboyd@kernel.org> Cc: Hans de Goede <hdegoede@redhat.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Wang Weiyang <wangweiyang2@huawei.com> Cc: Yang Yingliang <yangyingliang@huawei.com> Cc: Jakob Koschel <jakobkoschel@gmail.com> Cc: Cai Xinchen <caixinchen1@huawei.com> Acked-by: Rafael J. Wysocki <rafael@kernel.org> Acked-by: Logan Gunthorpe <logang@deltatee.com> Link: https://lore.kernel.org/r/2023040250-pushover-platter-509c@gregkh Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1449 lines
35 KiB
C
1449 lines
35 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* ds.c -- 16-bit PCMCIA core support
|
|
*
|
|
* 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.
|
|
*
|
|
* (C) 1999 David A. Hinds
|
|
* (C) 2003 - 2010 Dominik Brodowski
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/list.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <pcmcia/cistpl.h>
|
|
#include <pcmcia/ds.h>
|
|
#include <pcmcia/ss.h>
|
|
|
|
#include "cs_internal.h"
|
|
|
|
/*====================================================================*/
|
|
|
|
/* Module parameters */
|
|
|
|
MODULE_AUTHOR("David Hinds <dahinds@users.sourceforge.net>");
|
|
MODULE_DESCRIPTION("PCMCIA Driver Services");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
/*====================================================================*/
|
|
|
|
static void pcmcia_check_driver(struct pcmcia_driver *p_drv)
|
|
{
|
|
const struct pcmcia_device_id *did = p_drv->id_table;
|
|
unsigned int i;
|
|
u32 hash;
|
|
|
|
if (!p_drv->probe || !p_drv->remove)
|
|
printk(KERN_DEBUG "pcmcia: %s lacks a requisite callback "
|
|
"function\n", p_drv->name);
|
|
|
|
while (did && did->match_flags) {
|
|
for (i = 0; i < 4; i++) {
|
|
if (!did->prod_id[i])
|
|
continue;
|
|
|
|
hash = crc32(0, did->prod_id[i], strlen(did->prod_id[i]));
|
|
if (hash == did->prod_id_hash[i])
|
|
continue;
|
|
|
|
printk(KERN_DEBUG "pcmcia: %s: invalid hash for "
|
|
"product string \"%s\": is 0x%x, should "
|
|
"be 0x%x\n", p_drv->name, did->prod_id[i],
|
|
did->prod_id_hash[i], hash);
|
|
printk(KERN_DEBUG "pcmcia: see "
|
|
"Documentation/pcmcia/devicetable.rst for "
|
|
"details\n");
|
|
}
|
|
did++;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*======================================================================*/
|
|
|
|
|
|
struct pcmcia_dynid {
|
|
struct list_head node;
|
|
struct pcmcia_device_id id;
|
|
};
|
|
|
|
/**
|
|
* new_id_store() - add a new PCMCIA device ID to this driver and re-probe devices
|
|
* @driver: target device driver
|
|
* @buf: buffer for scanning device ID data
|
|
* @count: input size
|
|
*
|
|
* Adds a new dynamic PCMCIA device ID to this driver,
|
|
* and causes the driver to probe for all devices again.
|
|
*/
|
|
static ssize_t
|
|
new_id_store(struct device_driver *driver, const char *buf, size_t count)
|
|
{
|
|
struct pcmcia_dynid *dynid;
|
|
struct pcmcia_driver *pdrv = to_pcmcia_drv(driver);
|
|
__u16 match_flags, manf_id, card_id;
|
|
__u8 func_id, function, device_no;
|
|
__u32 prod_id_hash[4] = {0, 0, 0, 0};
|
|
int fields = 0;
|
|
int retval = 0;
|
|
|
|
fields = sscanf(buf, "%hx %hx %hx %hhx %hhx %hhx %x %x %x %x",
|
|
&match_flags, &manf_id, &card_id, &func_id, &function, &device_no,
|
|
&prod_id_hash[0], &prod_id_hash[1], &prod_id_hash[2], &prod_id_hash[3]);
|
|
if (fields < 6)
|
|
return -EINVAL;
|
|
|
|
dynid = kzalloc(sizeof(struct pcmcia_dynid), GFP_KERNEL);
|
|
if (!dynid)
|
|
return -ENOMEM;
|
|
|
|
dynid->id.match_flags = match_flags;
|
|
dynid->id.manf_id = manf_id;
|
|
dynid->id.card_id = card_id;
|
|
dynid->id.func_id = func_id;
|
|
dynid->id.function = function;
|
|
dynid->id.device_no = device_no;
|
|
memcpy(dynid->id.prod_id_hash, prod_id_hash, sizeof(__u32) * 4);
|
|
|
|
mutex_lock(&pdrv->dynids.lock);
|
|
list_add_tail(&dynid->node, &pdrv->dynids.list);
|
|
mutex_unlock(&pdrv->dynids.lock);
|
|
|
|
retval = driver_attach(&pdrv->drv);
|
|
|
|
if (retval)
|
|
return retval;
|
|
return count;
|
|
}
|
|
static DRIVER_ATTR_WO(new_id);
|
|
|
|
static void
|
|
pcmcia_free_dynids(struct pcmcia_driver *drv)
|
|
{
|
|
struct pcmcia_dynid *dynid, *n;
|
|
|
|
mutex_lock(&drv->dynids.lock);
|
|
list_for_each_entry_safe(dynid, n, &drv->dynids.list, node) {
|
|
list_del(&dynid->node);
|
|
kfree(dynid);
|
|
}
|
|
mutex_unlock(&drv->dynids.lock);
|
|
}
|
|
|
|
static int
|
|
pcmcia_create_newid_file(struct pcmcia_driver *drv)
|
|
{
|
|
int error = 0;
|
|
if (drv->probe != NULL)
|
|
error = driver_create_file(&drv->drv, &driver_attr_new_id);
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
pcmcia_remove_newid_file(struct pcmcia_driver *drv)
|
|
{
|
|
driver_remove_file(&drv->drv, &driver_attr_new_id);
|
|
}
|
|
|
|
/**
|
|
* pcmcia_register_driver - register a PCMCIA driver with the bus core
|
|
* @driver: the &driver being registered
|
|
*
|
|
* Registers a PCMCIA driver with the PCMCIA bus core.
|
|
*/
|
|
int pcmcia_register_driver(struct pcmcia_driver *driver)
|
|
{
|
|
int error;
|
|
|
|
if (!driver)
|
|
return -EINVAL;
|
|
|
|
pcmcia_check_driver(driver);
|
|
|
|
/* initialize common fields */
|
|
driver->drv.bus = &pcmcia_bus_type;
|
|
driver->drv.owner = driver->owner;
|
|
driver->drv.name = driver->name;
|
|
mutex_init(&driver->dynids.lock);
|
|
INIT_LIST_HEAD(&driver->dynids.list);
|
|
|
|
pr_debug("registering driver %s\n", driver->name);
|
|
|
|
error = driver_register(&driver->drv);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
error = pcmcia_create_newid_file(driver);
|
|
if (error)
|
|
driver_unregister(&driver->drv);
|
|
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(pcmcia_register_driver);
|
|
|
|
/**
|
|
* pcmcia_unregister_driver - unregister a PCMCIA driver with the bus core
|
|
* @driver: the &driver being unregistered
|
|
*/
|
|
void pcmcia_unregister_driver(struct pcmcia_driver *driver)
|
|
{
|
|
pr_debug("unregistering driver %s\n", driver->name);
|
|
pcmcia_remove_newid_file(driver);
|
|
driver_unregister(&driver->drv);
|
|
pcmcia_free_dynids(driver);
|
|
}
|
|
EXPORT_SYMBOL(pcmcia_unregister_driver);
|
|
|
|
|
|
/* pcmcia_device handling */
|
|
|
|
static struct pcmcia_device *pcmcia_get_dev(struct pcmcia_device *p_dev)
|
|
{
|
|
struct device *tmp_dev;
|
|
tmp_dev = get_device(&p_dev->dev);
|
|
if (!tmp_dev)
|
|
return NULL;
|
|
return to_pcmcia_dev(tmp_dev);
|
|
}
|
|
|
|
static void pcmcia_put_dev(struct pcmcia_device *p_dev)
|
|
{
|
|
if (p_dev)
|
|
put_device(&p_dev->dev);
|
|
}
|
|
|
|
static void pcmcia_release_function(struct kref *ref)
|
|
{
|
|
struct config_t *c = container_of(ref, struct config_t, ref);
|
|
pr_debug("releasing config_t\n");
|
|
kfree(c);
|
|
}
|
|
|
|
static void pcmcia_release_dev(struct device *dev)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
int i;
|
|
dev_dbg(dev, "releasing device\n");
|
|
pcmcia_put_socket(p_dev->socket);
|
|
for (i = 0; i < 4; i++)
|
|
kfree(p_dev->prod_id[i]);
|
|
kfree(p_dev->devname);
|
|
kref_put(&p_dev->function_config->ref, pcmcia_release_function);
|
|
kfree(p_dev);
|
|
}
|
|
|
|
|
|
static int pcmcia_device_probe(struct device *dev)
|
|
{
|
|
struct pcmcia_device *p_dev;
|
|
struct pcmcia_driver *p_drv;
|
|
struct pcmcia_socket *s;
|
|
cistpl_config_t cis_config;
|
|
int ret = 0;
|
|
|
|
dev = get_device(dev);
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
p_dev = to_pcmcia_dev(dev);
|
|
p_drv = to_pcmcia_drv(dev->driver);
|
|
s = p_dev->socket;
|
|
|
|
dev_dbg(dev, "trying to bind to %s\n", p_drv->name);
|
|
|
|
if ((!p_drv->probe) || (!p_dev->function_config) ||
|
|
(!try_module_get(p_drv->owner))) {
|
|
ret = -EINVAL;
|
|
goto put_dev;
|
|
}
|
|
|
|
/* set up some more device information */
|
|
ret = pccard_read_tuple(p_dev->socket, p_dev->func, CISTPL_CONFIG,
|
|
&cis_config);
|
|
if (!ret) {
|
|
p_dev->config_base = cis_config.base;
|
|
p_dev->config_regs = cis_config.rmask[0];
|
|
dev_dbg(dev, "base %x, regs %x", p_dev->config_base,
|
|
p_dev->config_regs);
|
|
} else {
|
|
dev_info(dev,
|
|
"pcmcia: could not parse base and rmask0 of CIS\n");
|
|
p_dev->config_base = 0;
|
|
p_dev->config_regs = 0;
|
|
}
|
|
|
|
ret = p_drv->probe(p_dev);
|
|
if (ret) {
|
|
dev_dbg(dev, "binding to %s failed with %d\n",
|
|
p_drv->name, ret);
|
|
goto put_module;
|
|
}
|
|
dev_dbg(dev, "%s bound: Vpp %d.%d, idx %x, IRQ %d", p_drv->name,
|
|
p_dev->vpp/10, p_dev->vpp%10, p_dev->config_index, p_dev->irq);
|
|
dev_dbg(dev, "resources: ioport %pR %pR iomem %pR %pR %pR",
|
|
p_dev->resource[0], p_dev->resource[1], p_dev->resource[2],
|
|
p_dev->resource[3], p_dev->resource[4]);
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
if ((s->pcmcia_pfc) &&
|
|
(p_dev->socket->device_count == 1) && (p_dev->device_no == 0))
|
|
pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
put_module:
|
|
if (ret)
|
|
module_put(p_drv->owner);
|
|
put_dev:
|
|
if (ret)
|
|
put_device(dev);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Removes a PCMCIA card from the device tree and socket list.
|
|
*/
|
|
static void pcmcia_card_remove(struct pcmcia_socket *s, struct pcmcia_device *leftover)
|
|
{
|
|
struct pcmcia_device *p_dev;
|
|
struct pcmcia_device *tmp;
|
|
|
|
dev_dbg(leftover ? &leftover->dev : &s->dev,
|
|
"pcmcia_card_remove(%d) %s\n", s->sock,
|
|
leftover ? leftover->devname : "");
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
if (!leftover)
|
|
s->device_count = 0;
|
|
else
|
|
s->device_count = 1;
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
/* unregister all pcmcia_devices registered with this socket, except leftover */
|
|
list_for_each_entry_safe(p_dev, tmp, &s->devices_list, socket_device_list) {
|
|
if (p_dev == leftover)
|
|
continue;
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
list_del(&p_dev->socket_device_list);
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
dev_dbg(&p_dev->dev, "unregistering device\n");
|
|
device_unregister(&p_dev->dev);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void pcmcia_device_remove(struct device *dev)
|
|
{
|
|
struct pcmcia_device *p_dev;
|
|
struct pcmcia_driver *p_drv;
|
|
int i;
|
|
|
|
p_dev = to_pcmcia_dev(dev);
|
|
p_drv = to_pcmcia_drv(dev->driver);
|
|
|
|
dev_dbg(dev, "removing device\n");
|
|
|
|
/* If we're removing the primary module driving a
|
|
* pseudo multi-function card, we need to unbind
|
|
* all devices
|
|
*/
|
|
if ((p_dev->socket->pcmcia_pfc) &&
|
|
(p_dev->socket->device_count > 0) &&
|
|
(p_dev->device_no == 0))
|
|
pcmcia_card_remove(p_dev->socket, p_dev);
|
|
|
|
/* detach the "instance" */
|
|
if (p_drv->remove)
|
|
p_drv->remove(p_dev);
|
|
|
|
/* check for proper unloading */
|
|
if (p_dev->_irq || p_dev->_io || p_dev->_locked)
|
|
dev_info(dev,
|
|
"pcmcia: driver %s did not release config properly\n",
|
|
p_drv->name);
|
|
|
|
for (i = 0; i < MAX_WIN; i++)
|
|
if (p_dev->_win & CLIENT_WIN_REQ(i))
|
|
dev_info(dev,
|
|
"pcmcia: driver %s did not release window properly\n",
|
|
p_drv->name);
|
|
|
|
/* references from pcmcia_device_probe */
|
|
pcmcia_put_dev(p_dev);
|
|
module_put(p_drv->owner);
|
|
}
|
|
|
|
|
|
/*
|
|
* pcmcia_device_query -- determine information about a pcmcia device
|
|
*/
|
|
static int pcmcia_device_query(struct pcmcia_device *p_dev)
|
|
{
|
|
cistpl_manfid_t manf_id;
|
|
cistpl_funcid_t func_id;
|
|
cistpl_vers_1_t *vers1;
|
|
unsigned int i;
|
|
|
|
vers1 = kmalloc(sizeof(*vers1), GFP_KERNEL);
|
|
if (!vers1)
|
|
return -ENOMEM;
|
|
|
|
if (!pccard_read_tuple(p_dev->socket, BIND_FN_ALL,
|
|
CISTPL_MANFID, &manf_id)) {
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
p_dev->manf_id = manf_id.manf;
|
|
p_dev->card_id = manf_id.card;
|
|
p_dev->has_manf_id = 1;
|
|
p_dev->has_card_id = 1;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
}
|
|
|
|
if (!pccard_read_tuple(p_dev->socket, p_dev->func,
|
|
CISTPL_FUNCID, &func_id)) {
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
p_dev->func_id = func_id.func;
|
|
p_dev->has_func_id = 1;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
} else {
|
|
/* rule of thumb: cards with no FUNCID, but with
|
|
* common memory device geometry information, are
|
|
* probably memory cards (from pcmcia-cs) */
|
|
cistpl_device_geo_t *devgeo;
|
|
|
|
devgeo = kmalloc(sizeof(*devgeo), GFP_KERNEL);
|
|
if (!devgeo) {
|
|
kfree(vers1);
|
|
return -ENOMEM;
|
|
}
|
|
if (!pccard_read_tuple(p_dev->socket, p_dev->func,
|
|
CISTPL_DEVICE_GEO, devgeo)) {
|
|
dev_dbg(&p_dev->dev,
|
|
"mem device geometry probably means "
|
|
"FUNCID_MEMORY\n");
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
p_dev->func_id = CISTPL_FUNCID_MEMORY;
|
|
p_dev->has_func_id = 1;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
}
|
|
kfree(devgeo);
|
|
}
|
|
|
|
if (!pccard_read_tuple(p_dev->socket, BIND_FN_ALL, CISTPL_VERS_1,
|
|
vers1)) {
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
for (i = 0; i < min_t(unsigned int, 4, vers1->ns); i++) {
|
|
char *tmp;
|
|
unsigned int length;
|
|
char *new;
|
|
|
|
tmp = vers1->str + vers1->ofs[i];
|
|
|
|
length = strlen(tmp) + 1;
|
|
if ((length < 2) || (length > 255))
|
|
continue;
|
|
|
|
new = kstrdup(tmp, GFP_KERNEL);
|
|
if (!new)
|
|
continue;
|
|
|
|
tmp = p_dev->prod_id[i];
|
|
p_dev->prod_id[i] = new;
|
|
kfree(tmp);
|
|
}
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
}
|
|
|
|
kfree(vers1);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static struct pcmcia_device *pcmcia_device_add(struct pcmcia_socket *s,
|
|
unsigned int function)
|
|
{
|
|
struct pcmcia_device *p_dev, *tmp_dev;
|
|
int i;
|
|
|
|
s = pcmcia_get_socket(s);
|
|
if (!s)
|
|
return NULL;
|
|
|
|
pr_debug("adding device to %d, function %d\n", s->sock, function);
|
|
|
|
p_dev = kzalloc(sizeof(struct pcmcia_device), GFP_KERNEL);
|
|
if (!p_dev)
|
|
goto err_put;
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
p_dev->device_no = (s->device_count++);
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
/* max of 2 PFC devices */
|
|
if ((p_dev->device_no >= 2) && (function == 0))
|
|
goto err_free;
|
|
|
|
/* max of 4 devices overall */
|
|
if (p_dev->device_no >= 4)
|
|
goto err_free;
|
|
|
|
p_dev->socket = s;
|
|
p_dev->func = function;
|
|
|
|
p_dev->dev.bus = &pcmcia_bus_type;
|
|
p_dev->dev.parent = s->dev.parent;
|
|
p_dev->dev.release = pcmcia_release_dev;
|
|
/* by default don't allow DMA */
|
|
p_dev->dma_mask = 0;
|
|
p_dev->dev.dma_mask = &p_dev->dma_mask;
|
|
dev_set_name(&p_dev->dev, "%d.%d", p_dev->socket->sock, p_dev->device_no);
|
|
if (!dev_name(&p_dev->dev))
|
|
goto err_free;
|
|
p_dev->devname = kasprintf(GFP_KERNEL, "pcmcia%s", dev_name(&p_dev->dev));
|
|
if (!p_dev->devname)
|
|
goto err_free;
|
|
dev_dbg(&p_dev->dev, "devname is %s\n", p_dev->devname);
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
|
|
/*
|
|
* p_dev->function_config must be the same for all card functions.
|
|
* Note that this is serialized by ops_mutex, so that only one
|
|
* such struct will be created.
|
|
*/
|
|
list_for_each_entry(tmp_dev, &s->devices_list, socket_device_list)
|
|
if (p_dev->func == tmp_dev->func) {
|
|
p_dev->function_config = tmp_dev->function_config;
|
|
p_dev->irq = tmp_dev->irq;
|
|
kref_get(&p_dev->function_config->ref);
|
|
}
|
|
|
|
/* Add to the list in pcmcia_bus_socket */
|
|
list_add(&p_dev->socket_device_list, &s->devices_list);
|
|
|
|
if (pcmcia_setup_irq(p_dev))
|
|
dev_warn(&p_dev->dev,
|
|
"IRQ setup failed -- device might not work\n");
|
|
|
|
if (!p_dev->function_config) {
|
|
config_t *c;
|
|
dev_dbg(&p_dev->dev, "creating config_t\n");
|
|
c = kzalloc(sizeof(struct config_t), GFP_KERNEL);
|
|
if (!c) {
|
|
mutex_unlock(&s->ops_mutex);
|
|
goto err_unreg;
|
|
}
|
|
p_dev->function_config = c;
|
|
kref_init(&c->ref);
|
|
for (i = 0; i < MAX_IO_WIN; i++) {
|
|
c->io[i].name = p_dev->devname;
|
|
c->io[i].flags = IORESOURCE_IO;
|
|
}
|
|
for (i = 0; i < MAX_WIN; i++) {
|
|
c->mem[i].name = p_dev->devname;
|
|
c->mem[i].flags = IORESOURCE_MEM;
|
|
}
|
|
}
|
|
for (i = 0; i < MAX_IO_WIN; i++)
|
|
p_dev->resource[i] = &p_dev->function_config->io[i];
|
|
for (; i < (MAX_IO_WIN + MAX_WIN); i++)
|
|
p_dev->resource[i] = &p_dev->function_config->mem[i-MAX_IO_WIN];
|
|
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
dev_notice(&p_dev->dev, "pcmcia: registering new device %s (IRQ: %d)\n",
|
|
p_dev->devname, p_dev->irq);
|
|
|
|
pcmcia_device_query(p_dev);
|
|
|
|
if (device_register(&p_dev->dev))
|
|
goto err_unreg;
|
|
|
|
return p_dev;
|
|
|
|
err_unreg:
|
|
mutex_lock(&s->ops_mutex);
|
|
list_del(&p_dev->socket_device_list);
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
err_free:
|
|
mutex_lock(&s->ops_mutex);
|
|
s->device_count--;
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
kfree(p_dev->prod_id[i]);
|
|
kfree(p_dev->devname);
|
|
kfree(p_dev);
|
|
err_put:
|
|
pcmcia_put_socket(s);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int pcmcia_card_add(struct pcmcia_socket *s)
|
|
{
|
|
cistpl_longlink_mfc_t mfc;
|
|
unsigned int no_funcs, i, no_chains;
|
|
int ret = -EAGAIN;
|
|
|
|
mutex_lock(&s->ops_mutex);
|
|
if (!(s->resource_setup_done)) {
|
|
dev_dbg(&s->dev,
|
|
"no resources available, delaying card_add\n");
|
|
mutex_unlock(&s->ops_mutex);
|
|
return -EAGAIN; /* try again, but later... */
|
|
}
|
|
|
|
if (pcmcia_validate_mem(s)) {
|
|
dev_dbg(&s->dev, "validating mem resources failed, "
|
|
"delaying card_add\n");
|
|
mutex_unlock(&s->ops_mutex);
|
|
return -EAGAIN; /* try again, but later... */
|
|
}
|
|
mutex_unlock(&s->ops_mutex);
|
|
|
|
ret = pccard_validate_cis(s, &no_chains);
|
|
if (ret || !no_chains) {
|
|
#if defined(CONFIG_MTD_PCMCIA_ANONYMOUS)
|
|
/* Set up as an anonymous card. If we don't have anonymous
|
|
memory support then just error the card as there is no
|
|
point trying to second guess.
|
|
|
|
Note: some cards have just a device entry, it may be
|
|
worth extending support to cover these in future */
|
|
if (ret == -EIO) {
|
|
dev_info(&s->dev, "no CIS, assuming an anonymous memory card.\n");
|
|
pcmcia_replace_cis(s, "\xFF", 1);
|
|
no_chains = 1;
|
|
ret = 0;
|
|
} else
|
|
#endif
|
|
{
|
|
dev_dbg(&s->dev, "invalid CIS or invalid resources\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC, &mfc))
|
|
no_funcs = mfc.nfn;
|
|
else
|
|
no_funcs = 1;
|
|
s->functions = no_funcs;
|
|
|
|
for (i = 0; i < no_funcs; i++)
|
|
pcmcia_device_add(s, i);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int pcmcia_requery_callback(struct device *dev, void *_data)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
if (!p_dev->dev.driver) {
|
|
dev_dbg(dev, "update device information\n");
|
|
pcmcia_device_query(p_dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void pcmcia_requery(struct pcmcia_socket *s)
|
|
{
|
|
int has_pfc;
|
|
|
|
if (!(s->state & SOCKET_PRESENT))
|
|
return;
|
|
|
|
if (s->functions == 0) {
|
|
pcmcia_card_add(s);
|
|
return;
|
|
}
|
|
|
|
/* some device information might have changed because of a CIS
|
|
* update or because we can finally read it correctly... so
|
|
* determine it again, overwriting old values if necessary. */
|
|
bus_for_each_dev(&pcmcia_bus_type, NULL, NULL, pcmcia_requery_callback);
|
|
|
|
/* if the CIS changed, we need to check whether the number of
|
|
* functions changed. */
|
|
if (s->fake_cis) {
|
|
int old_funcs, new_funcs;
|
|
cistpl_longlink_mfc_t mfc;
|
|
|
|
/* does this cis override add or remove functions? */
|
|
old_funcs = s->functions;
|
|
|
|
if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC,
|
|
&mfc))
|
|
new_funcs = mfc.nfn;
|
|
else
|
|
new_funcs = 1;
|
|
if (old_funcs != new_funcs) {
|
|
/* we need to re-start */
|
|
pcmcia_card_remove(s, NULL);
|
|
s->functions = 0;
|
|
pcmcia_card_add(s);
|
|
}
|
|
}
|
|
|
|
/* If the PCMCIA device consists of two pseudo devices,
|
|
* call pcmcia_device_add() -- which will fail if both
|
|
* devices are already registered. */
|
|
mutex_lock(&s->ops_mutex);
|
|
has_pfc = s->pcmcia_pfc;
|
|
mutex_unlock(&s->ops_mutex);
|
|
if (has_pfc)
|
|
pcmcia_device_add(s, 0);
|
|
|
|
/* we re-scan all devices, not just the ones connected to this
|
|
* socket. This does not matter, though. */
|
|
if (bus_rescan_devices(&pcmcia_bus_type))
|
|
dev_warn(&s->dev, "rescanning the bus failed\n");
|
|
}
|
|
|
|
|
|
#ifdef CONFIG_PCMCIA_LOAD_CIS
|
|
|
|
/**
|
|
* pcmcia_load_firmware - load CIS from userspace if device-provided is broken
|
|
* @dev: the pcmcia device which needs a CIS override
|
|
* @filename: requested filename in /lib/firmware/
|
|
*
|
|
* This uses the in-kernel firmware loading mechanism to use a "fake CIS" if
|
|
* the one provided by the card is broken. The firmware files reside in
|
|
* /lib/firmware/ in userspace.
|
|
*/
|
|
static int pcmcia_load_firmware(struct pcmcia_device *dev, char *filename)
|
|
{
|
|
struct pcmcia_socket *s = dev->socket;
|
|
const struct firmware *fw;
|
|
int ret = -ENOMEM;
|
|
cistpl_longlink_mfc_t mfc;
|
|
int old_funcs, new_funcs = 1;
|
|
|
|
if (!filename)
|
|
return -EINVAL;
|
|
|
|
dev_dbg(&dev->dev, "trying to load CIS file %s\n", filename);
|
|
|
|
if (request_firmware(&fw, filename, &dev->dev) == 0) {
|
|
if (fw->size >= CISTPL_MAX_CIS_SIZE) {
|
|
ret = -EINVAL;
|
|
dev_err(&dev->dev, "pcmcia: CIS override is too big\n");
|
|
goto release;
|
|
}
|
|
|
|
if (!pcmcia_replace_cis(s, fw->data, fw->size))
|
|
ret = 0;
|
|
else {
|
|
dev_err(&dev->dev, "pcmcia: CIS override failed\n");
|
|
goto release;
|
|
}
|
|
|
|
/* we need to re-start if the number of functions changed */
|
|
old_funcs = s->functions;
|
|
if (!pccard_read_tuple(s, BIND_FN_ALL, CISTPL_LONGLINK_MFC,
|
|
&mfc))
|
|
new_funcs = mfc.nfn;
|
|
|
|
if (old_funcs != new_funcs)
|
|
ret = -EBUSY;
|
|
|
|
/* update information */
|
|
pcmcia_device_query(dev);
|
|
|
|
/* requery (as number of functions might have changed) */
|
|
pcmcia_parse_uevents(s, PCMCIA_UEVENT_REQUERY);
|
|
}
|
|
release:
|
|
release_firmware(fw);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#else /* !CONFIG_PCMCIA_LOAD_CIS */
|
|
|
|
static inline int pcmcia_load_firmware(struct pcmcia_device *dev,
|
|
char *filename)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static inline int pcmcia_devmatch(struct pcmcia_device *dev,
|
|
const struct pcmcia_device_id *did)
|
|
{
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID) {
|
|
if ((!dev->has_manf_id) || (dev->manf_id != did->manf_id))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID) {
|
|
if ((!dev->has_card_id) || (dev->card_id != did->card_id))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION) {
|
|
if (dev->func != did->function)
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1) {
|
|
if (!dev->prod_id[0])
|
|
return 0;
|
|
if (strcmp(did->prod_id[0], dev->prod_id[0]))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2) {
|
|
if (!dev->prod_id[1])
|
|
return 0;
|
|
if (strcmp(did->prod_id[1], dev->prod_id[1]))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3) {
|
|
if (!dev->prod_id[2])
|
|
return 0;
|
|
if (strcmp(did->prod_id[2], dev->prod_id[2]))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4) {
|
|
if (!dev->prod_id[3])
|
|
return 0;
|
|
if (strcmp(did->prod_id[3], dev->prod_id[3]))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) {
|
|
dev_dbg(&dev->dev, "this is a pseudo-multi-function device\n");
|
|
mutex_lock(&dev->socket->ops_mutex);
|
|
dev->socket->pcmcia_pfc = 1;
|
|
mutex_unlock(&dev->socket->ops_mutex);
|
|
if (dev->device_no != did->device_no)
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID) {
|
|
int ret;
|
|
|
|
if ((!dev->has_func_id) || (dev->func_id != did->func_id))
|
|
return 0;
|
|
|
|
/* if this is a pseudo-multi-function device,
|
|
* we need explicit matches */
|
|
if (dev->socket->pcmcia_pfc)
|
|
return 0;
|
|
if (dev->device_no)
|
|
return 0;
|
|
|
|
/* also, FUNC_ID matching needs to be activated by userspace
|
|
* after it has re-checked that there is no possible module
|
|
* with a prod_id/manf_id/card_id match.
|
|
*/
|
|
mutex_lock(&dev->socket->ops_mutex);
|
|
ret = dev->allow_func_id_match;
|
|
mutex_unlock(&dev->socket->ops_mutex);
|
|
|
|
if (!ret) {
|
|
dev_dbg(&dev->dev,
|
|
"skipping FUNC_ID match until userspace ACK\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) {
|
|
dev_dbg(&dev->dev, "device needs a fake CIS\n");
|
|
if (!dev->socket->fake_cis)
|
|
if (pcmcia_load_firmware(dev, did->cisfile))
|
|
return 0;
|
|
}
|
|
|
|
if (did->match_flags & PCMCIA_DEV_ID_MATCH_ANONYMOUS) {
|
|
int i;
|
|
for (i = 0; i < 4; i++)
|
|
if (dev->prod_id[i])
|
|
return 0;
|
|
if (dev->has_manf_id || dev->has_card_id || dev->has_func_id)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int pcmcia_bus_match(struct device *dev, struct device_driver *drv)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
struct pcmcia_driver *p_drv = to_pcmcia_drv(drv);
|
|
const struct pcmcia_device_id *did = p_drv->id_table;
|
|
struct pcmcia_dynid *dynid;
|
|
|
|
/* match dynamic devices first */
|
|
mutex_lock(&p_drv->dynids.lock);
|
|
list_for_each_entry(dynid, &p_drv->dynids.list, node) {
|
|
dev_dbg(dev, "trying to match to %s\n", drv->name);
|
|
if (pcmcia_devmatch(p_dev, &dynid->id)) {
|
|
dev_dbg(dev, "matched to %s\n", drv->name);
|
|
mutex_unlock(&p_drv->dynids.lock);
|
|
return 1;
|
|
}
|
|
}
|
|
mutex_unlock(&p_drv->dynids.lock);
|
|
|
|
while (did && did->match_flags) {
|
|
dev_dbg(dev, "trying to match to %s\n", drv->name);
|
|
if (pcmcia_devmatch(p_dev, did)) {
|
|
dev_dbg(dev, "matched to %s\n", drv->name);
|
|
return 1;
|
|
}
|
|
did++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
|
|
{
|
|
const struct pcmcia_device *p_dev;
|
|
int i;
|
|
u32 hash[4] = { 0, 0, 0, 0};
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
p_dev = to_pcmcia_dev(dev);
|
|
|
|
/* calculate hashes */
|
|
for (i = 0; i < 4; i++) {
|
|
if (!p_dev->prod_id[i])
|
|
continue;
|
|
hash[i] = crc32(0, p_dev->prod_id[i], strlen(p_dev->prod_id[i]));
|
|
}
|
|
|
|
if (add_uevent_var(env, "SOCKET_NO=%u", p_dev->socket->sock))
|
|
return -ENOMEM;
|
|
|
|
if (add_uevent_var(env, "DEVICE_NO=%02X", p_dev->device_no))
|
|
return -ENOMEM;
|
|
|
|
if (add_uevent_var(env, "MODALIAS=pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02X"
|
|
"pa%08Xpb%08Xpc%08Xpd%08X",
|
|
p_dev->has_manf_id ? p_dev->manf_id : 0,
|
|
p_dev->has_card_id ? p_dev->card_id : 0,
|
|
p_dev->has_func_id ? p_dev->func_id : 0,
|
|
p_dev->func,
|
|
p_dev->device_no,
|
|
hash[0],
|
|
hash[1],
|
|
hash[2],
|
|
hash[3]))
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/************************ runtime PM support ***************************/
|
|
|
|
static int pcmcia_dev_suspend(struct device *dev);
|
|
static int pcmcia_dev_resume(struct device *dev);
|
|
|
|
static int runtime_suspend(struct device *dev)
|
|
{
|
|
int rc;
|
|
|
|
device_lock(dev);
|
|
rc = pcmcia_dev_suspend(dev);
|
|
device_unlock(dev);
|
|
return rc;
|
|
}
|
|
|
|
static int runtime_resume(struct device *dev)
|
|
{
|
|
int rc;
|
|
|
|
device_lock(dev);
|
|
rc = pcmcia_dev_resume(dev);
|
|
device_unlock(dev);
|
|
return rc;
|
|
}
|
|
|
|
/************************ per-device sysfs output ***************************/
|
|
|
|
#define pcmcia_device_attr(field, test, format) \
|
|
static ssize_t field##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
|
{ \
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \
|
|
return p_dev->test ? sysfs_emit(buf, format, p_dev->field) : -ENODEV; \
|
|
} \
|
|
static DEVICE_ATTR_RO(field);
|
|
|
|
#define pcmcia_device_stringattr(name, field) \
|
|
static ssize_t name##_show (struct device *dev, struct device_attribute *attr, char *buf) \
|
|
{ \
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev); \
|
|
return p_dev->field ? sysfs_emit(buf, "%s\n", p_dev->field) : -ENODEV; \
|
|
} \
|
|
static DEVICE_ATTR_RO(name);
|
|
|
|
pcmcia_device_attr(func_id, has_func_id, "0x%02x\n");
|
|
pcmcia_device_attr(manf_id, has_manf_id, "0x%04x\n");
|
|
pcmcia_device_attr(card_id, has_card_id, "0x%04x\n");
|
|
pcmcia_device_stringattr(prod_id1, prod_id[0]);
|
|
pcmcia_device_stringattr(prod_id2, prod_id[1]);
|
|
pcmcia_device_stringattr(prod_id3, prod_id[2]);
|
|
pcmcia_device_stringattr(prod_id4, prod_id[3]);
|
|
|
|
static ssize_t function_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
return p_dev->socket ? sysfs_emit(buf, "0x%02x\n", p_dev->func) : -ENODEV;
|
|
}
|
|
static DEVICE_ATTR_RO(function);
|
|
|
|
static ssize_t resources_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
int i, at = 0;
|
|
|
|
for (i = 0; i < PCMCIA_NUM_RESOURCES; i++)
|
|
at += sysfs_emit_at(buf, at, "%pr\n", p_dev->resource[i]);
|
|
|
|
return at;
|
|
}
|
|
static DEVICE_ATTR_RO(resources);
|
|
|
|
static ssize_t pm_state_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
|
|
if (p_dev->suspended)
|
|
return sysfs_emit(buf, "off\n");
|
|
else
|
|
return sysfs_emit(buf, "on\n");
|
|
}
|
|
|
|
static ssize_t pm_state_store(struct device *dev, struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
int ret = 0;
|
|
|
|
if (!count)
|
|
return -EINVAL;
|
|
|
|
if ((!p_dev->suspended) && !strncmp(buf, "off", 3))
|
|
ret = runtime_suspend(dev);
|
|
else if (p_dev->suspended && !strncmp(buf, "on", 2))
|
|
ret = runtime_resume(dev);
|
|
|
|
return ret ? ret : count;
|
|
}
|
|
static DEVICE_ATTR_RW(pm_state);
|
|
|
|
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
int i;
|
|
u32 hash[4] = { 0, 0, 0, 0};
|
|
|
|
/* calculate hashes */
|
|
for (i = 0; i < 4; i++) {
|
|
if (!p_dev->prod_id[i])
|
|
continue;
|
|
hash[i] = crc32(0, p_dev->prod_id[i],
|
|
strlen(p_dev->prod_id[i]));
|
|
}
|
|
return sysfs_emit(buf, "pcmcia:m%04Xc%04Xf%02Xfn%02Xpfn%02Xpa%08Xpb%08Xpc%08Xpd%08X\n",
|
|
p_dev->has_manf_id ? p_dev->manf_id : 0,
|
|
p_dev->has_card_id ? p_dev->card_id : 0,
|
|
p_dev->has_func_id ? p_dev->func_id : 0,
|
|
p_dev->func, p_dev->device_no,
|
|
hash[0], hash[1], hash[2], hash[3]);
|
|
}
|
|
static DEVICE_ATTR_RO(modalias);
|
|
|
|
static ssize_t allow_func_id_match_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
|
|
if (!count)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
p_dev->allow_func_id_match = 1;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
pcmcia_parse_uevents(p_dev->socket, PCMCIA_UEVENT_REQUERY);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR_WO(allow_func_id_match);
|
|
|
|
static struct attribute *pcmcia_dev_attrs[] = {
|
|
&dev_attr_resources.attr,
|
|
&dev_attr_pm_state.attr,
|
|
&dev_attr_function.attr,
|
|
&dev_attr_func_id.attr,
|
|
&dev_attr_manf_id.attr,
|
|
&dev_attr_card_id.attr,
|
|
&dev_attr_prod_id1.attr,
|
|
&dev_attr_prod_id2.attr,
|
|
&dev_attr_prod_id3.attr,
|
|
&dev_attr_prod_id4.attr,
|
|
&dev_attr_modalias.attr,
|
|
&dev_attr_allow_func_id_match.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(pcmcia_dev);
|
|
|
|
/* PM support, also needed for reset */
|
|
|
|
static int pcmcia_dev_suspend(struct device *dev)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
struct pcmcia_driver *p_drv = NULL;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
if (p_dev->suspended) {
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
return 0;
|
|
}
|
|
p_dev->suspended = 1;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
|
|
dev_dbg(dev, "suspending\n");
|
|
|
|
if (dev->driver)
|
|
p_drv = to_pcmcia_drv(dev->driver);
|
|
|
|
if (!p_drv)
|
|
goto out;
|
|
|
|
if (p_drv->suspend) {
|
|
ret = p_drv->suspend(p_dev);
|
|
if (ret) {
|
|
dev_err(dev,
|
|
"pcmcia: device %s (driver %s) did not want to go to sleep (%d)\n",
|
|
p_dev->devname, p_drv->name, ret);
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
p_dev->suspended = 0;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (p_dev->device_no == p_dev->func) {
|
|
dev_dbg(dev, "releasing configuration\n");
|
|
pcmcia_release_configuration(p_dev);
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int pcmcia_dev_resume(struct device *dev)
|
|
{
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
struct pcmcia_driver *p_drv = NULL;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&p_dev->socket->ops_mutex);
|
|
if (!p_dev->suspended) {
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
return 0;
|
|
}
|
|
p_dev->suspended = 0;
|
|
mutex_unlock(&p_dev->socket->ops_mutex);
|
|
|
|
dev_dbg(dev, "resuming\n");
|
|
|
|
if (dev->driver)
|
|
p_drv = to_pcmcia_drv(dev->driver);
|
|
|
|
if (!p_drv)
|
|
goto out;
|
|
|
|
if (p_dev->device_no == p_dev->func) {
|
|
dev_dbg(dev, "requesting configuration\n");
|
|
ret = pcmcia_enable_device(p_dev);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
if (p_drv->resume)
|
|
ret = p_drv->resume(p_dev);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int pcmcia_bus_suspend_callback(struct device *dev, void *_data)
|
|
{
|
|
struct pcmcia_socket *skt = _data;
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
|
|
if (p_dev->socket != skt || p_dev->suspended)
|
|
return 0;
|
|
|
|
return runtime_suspend(dev);
|
|
}
|
|
|
|
static int pcmcia_bus_resume_callback(struct device *dev, void *_data)
|
|
{
|
|
struct pcmcia_socket *skt = _data;
|
|
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
|
|
|
|
if (p_dev->socket != skt || !p_dev->suspended)
|
|
return 0;
|
|
|
|
runtime_resume(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_resume(struct pcmcia_socket *skt)
|
|
{
|
|
dev_dbg(&skt->dev, "resuming socket %d\n", skt->sock);
|
|
bus_for_each_dev(&pcmcia_bus_type, NULL, skt, pcmcia_bus_resume_callback);
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_suspend(struct pcmcia_socket *skt)
|
|
{
|
|
dev_dbg(&skt->dev, "suspending socket %d\n", skt->sock);
|
|
if (bus_for_each_dev(&pcmcia_bus_type, NULL, skt,
|
|
pcmcia_bus_suspend_callback)) {
|
|
pcmcia_bus_resume(skt);
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_remove(struct pcmcia_socket *skt)
|
|
{
|
|
atomic_set(&skt->present, 0);
|
|
pcmcia_card_remove(skt, NULL);
|
|
|
|
mutex_lock(&skt->ops_mutex);
|
|
destroy_cis_cache(skt);
|
|
pcmcia_cleanup_irq(skt);
|
|
mutex_unlock(&skt->ops_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_add(struct pcmcia_socket *skt)
|
|
{
|
|
atomic_set(&skt->present, 1);
|
|
|
|
mutex_lock(&skt->ops_mutex);
|
|
skt->pcmcia_pfc = 0;
|
|
destroy_cis_cache(skt); /* to be on the safe side... */
|
|
mutex_unlock(&skt->ops_mutex);
|
|
|
|
pcmcia_card_add(skt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pcmcia_bus_early_resume(struct pcmcia_socket *skt)
|
|
{
|
|
if (!verify_cis_cache(skt))
|
|
return 0;
|
|
|
|
dev_dbg(&skt->dev, "cis mismatch - different card\n");
|
|
|
|
/* first, remove the card */
|
|
pcmcia_bus_remove(skt);
|
|
|
|
mutex_lock(&skt->ops_mutex);
|
|
destroy_cis_cache(skt);
|
|
kfree(skt->fake_cis);
|
|
skt->fake_cis = NULL;
|
|
skt->functions = 0;
|
|
mutex_unlock(&skt->ops_mutex);
|
|
|
|
/* now, add the new card */
|
|
pcmcia_bus_add(skt);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* NOTE: This is racy. There's no guarantee the card will still be
|
|
* physically present, even if the call to this function returns
|
|
* non-NULL. Furthermore, the device driver most likely is unbound
|
|
* almost immediately, so the timeframe where pcmcia_dev_present
|
|
* returns NULL is probably really really small.
|
|
*/
|
|
struct pcmcia_device *pcmcia_dev_present(struct pcmcia_device *_p_dev)
|
|
{
|
|
struct pcmcia_device *p_dev;
|
|
struct pcmcia_device *ret = NULL;
|
|
|
|
p_dev = pcmcia_get_dev(_p_dev);
|
|
if (!p_dev)
|
|
return NULL;
|
|
|
|
if (atomic_read(&p_dev->socket->present) != 0)
|
|
ret = p_dev;
|
|
|
|
pcmcia_put_dev(p_dev);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(pcmcia_dev_present);
|
|
|
|
|
|
static struct pcmcia_callback pcmcia_bus_callback = {
|
|
.owner = THIS_MODULE,
|
|
.add = pcmcia_bus_add,
|
|
.remove = pcmcia_bus_remove,
|
|
.requery = pcmcia_requery,
|
|
.validate = pccard_validate_cis,
|
|
.suspend = pcmcia_bus_suspend,
|
|
.early_resume = pcmcia_bus_early_resume,
|
|
.resume = pcmcia_bus_resume,
|
|
};
|
|
|
|
static int pcmcia_bus_add_socket(struct device *dev)
|
|
{
|
|
struct pcmcia_socket *socket = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
socket = pcmcia_get_socket(socket);
|
|
if (!socket) {
|
|
dev_err(dev, "PCMCIA obtaining reference to socket failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = sysfs_create_bin_file(&dev->kobj, &pccard_cis_attr);
|
|
if (ret) {
|
|
dev_err(dev, "PCMCIA registration failed\n");
|
|
pcmcia_put_socket(socket);
|
|
return ret;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&socket->devices_list);
|
|
socket->pcmcia_pfc = 0;
|
|
socket->device_count = 0;
|
|
atomic_set(&socket->present, 0);
|
|
|
|
ret = pccard_register_pcmcia(socket, &pcmcia_bus_callback);
|
|
if (ret) {
|
|
dev_err(dev, "PCMCIA registration failed\n");
|
|
pcmcia_put_socket(socket);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void pcmcia_bus_remove_socket(struct device *dev)
|
|
{
|
|
struct pcmcia_socket *socket = dev_get_drvdata(dev);
|
|
|
|
if (!socket)
|
|
return;
|
|
|
|
pccard_register_pcmcia(socket, NULL);
|
|
|
|
/* unregister any unbound devices */
|
|
mutex_lock(&socket->skt_mutex);
|
|
pcmcia_card_remove(socket, NULL);
|
|
release_cis_mem(socket);
|
|
mutex_unlock(&socket->skt_mutex);
|
|
|
|
sysfs_remove_bin_file(&dev->kobj, &pccard_cis_attr);
|
|
|
|
pcmcia_put_socket(socket);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* the pcmcia_bus_interface is used to handle pcmcia socket devices */
|
|
static struct class_interface pcmcia_bus_interface __refdata = {
|
|
.class = &pcmcia_socket_class,
|
|
.add_dev = &pcmcia_bus_add_socket,
|
|
.remove_dev = &pcmcia_bus_remove_socket,
|
|
};
|
|
|
|
static const struct dev_pm_ops pcmcia_bus_pm_ops = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(pcmcia_dev_suspend, pcmcia_dev_resume)
|
|
};
|
|
|
|
struct bus_type pcmcia_bus_type = {
|
|
.name = "pcmcia",
|
|
.uevent = pcmcia_bus_uevent,
|
|
.match = pcmcia_bus_match,
|
|
.dev_groups = pcmcia_dev_groups,
|
|
.probe = pcmcia_device_probe,
|
|
.remove = pcmcia_device_remove,
|
|
.pm = &pcmcia_bus_pm_ops,
|
|
};
|
|
|
|
|
|
static int __init init_pcmcia_bus(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = bus_register(&pcmcia_bus_type);
|
|
if (ret < 0) {
|
|
printk(KERN_WARNING "pcmcia: bus_register error: %d\n", ret);
|
|
return ret;
|
|
}
|
|
ret = class_interface_register(&pcmcia_bus_interface);
|
|
if (ret < 0) {
|
|
printk(KERN_WARNING
|
|
"pcmcia: class_interface_register error: %d\n", ret);
|
|
bus_unregister(&pcmcia_bus_type);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
fs_initcall(init_pcmcia_bus); /* one level after subsys_initcall so that
|
|
* pcmcia_socket_class is already registered */
|
|
|
|
|
|
static void __exit exit_pcmcia_bus(void)
|
|
{
|
|
class_interface_unregister(&pcmcia_bus_interface);
|
|
|
|
bus_unregister(&pcmcia_bus_type);
|
|
}
|
|
module_exit(exit_pcmcia_bus);
|
|
|
|
|
|
MODULE_ALIAS("ds");
|