* are Copyright (C) 1999 David A. Hinds. All Rights Reserved.
*
* (C) 1999 David A. Hinds
- * (C) 2003 - 2005 Dominik Brodowski
+ * (C) 2003 - 2006 Dominik Brodowski
*/
-#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/crc32.h>
#include <linux/firmware.h>
+#include <linux/kref.h>
#define IN_CARD_SERVICES
#include <pcmcia/cs_types.h>
/* initialize common fields */
driver->drv.bus = &pcmcia_bus_type;
driver->drv.owner = driver->owner;
- driver->drv.probe = pcmcia_device_probe;
- driver->drv.remove = pcmcia_device_remove;
return driver_register(&driver->drv);
}
put_device(&p_dev->dev);
}
+static void pcmcia_release_function(struct kref *ref)
+{
+ struct config_t *c = container_of(ref, struct config_t, ref);
+ kfree(c);
+}
+
static void pcmcia_release_dev(struct device *dev)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
ds_dbg(1, "releasing dev %p\n", p_dev);
pcmcia_put_socket(p_dev->socket);
kfree(p_dev->devname);
+ kref_put(&p_dev->function_config->ref, pcmcia_release_function);
kfree(p_dev);
}
+static void pcmcia_add_pseudo_device(struct pcmcia_socket *s)
+{
+ if (!s->pcmcia_state.device_add_pending) {
+ s->pcmcia_state.device_add_pending = 1;
+ schedule_work(&s->device_add);
+ }
+ return;
+}
static int pcmcia_device_probe(struct device * dev)
{
struct pcmcia_device *p_dev;
struct pcmcia_driver *p_drv;
+ struct pcmcia_device_id *did;
struct pcmcia_socket *s;
int ret = 0;
p_drv = to_pcmcia_drv(dev->driver);
s = p_dev->socket;
- if ((!p_drv->probe) || (!try_module_get(p_drv->owner))) {
+ if ((!p_drv->probe) || (!p_dev->function_config) ||
+ (!try_module_get(p_drv->owner))) {
ret = -EINVAL;
goto put_dev;
}
- p_dev->state &= ~CLIENT_UNBOUND;
-
- /* set up the device configuration, if it hasn't been done before */
- if (!s->functions) {
- cistpl_longlink_mfc_t mfc;
- if (pccard_read_tuple(s, p_dev->func, CISTPL_LONGLINK_MFC,
- &mfc) == CS_SUCCESS)
- s->functions = mfc.nfn;
- else
- s->functions = 1;
- s->config = kzalloc(sizeof(config_t) * s->functions,
- GFP_KERNEL);
- if (!s->config) {
- ret = -ENOMEM;
- goto put_module;
- }
- }
+ p_dev->p_state &= ~CLIENT_UNBOUND;
+ p_dev->handle = p_dev;
ret = p_drv->probe(p_dev);
+ if (ret)
+ goto put_module;
+
+ /* handle pseudo multifunction devices:
+ * there are at most two pseudo multifunction devices.
+ * if we're matching against the first, schedule a
+ * call which will then check whether there are two
+ * pseudo devices, and if not, add the second one.
+ */
+ did = (struct pcmcia_device_id *) p_dev->dev.driver_data;
+ if (did && (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) &&
+ (p_dev->socket->device_count == 1) && (p_dev->device_no == 0))
+ pcmcia_add_pseudo_device(p_dev->socket);
put_module:
if (ret)
p_drv->remove(p_dev);
/* check for proper unloading */
- if (p_dev->state & (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED))
+ if (p_dev->p_state & (CLIENT_IRQ_REQ|CLIENT_IO_REQ|CLIENT_CONFIG_LOCKED))
printk(KERN_INFO "pcmcia: driver %s did not release config properly\n",
p_drv->drv.name);
for (i = 0; i < MAX_WIN; i++)
- if (p_dev->state & CLIENT_WIN_REQ(i))
+ if (p_dev->p_state & CLIENT_WIN_REQ(i))
printk(KERN_INFO "pcmcia: driver %s did not release windows properly\n",
p_drv->drv.name);
/* references from pcmcia_probe_device */
- p_dev->state = CLIENT_UNBOUND;
+ p_dev->p_state = CLIENT_UNBOUND;
pcmcia_put_dev(p_dev);
module_put(p_drv->owner);
}
p_dev = list_entry((&s->devices_list)->next, struct pcmcia_device, socket_device_list);
list_del(&p_dev->socket_device_list);
- p_dev->state |= CLIENT_STALE;
+ p_dev->p_state |= CLIENT_STALE;
spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
device_unregister(&p_dev->dev);
tmp = vers1->str + vers1->ofs[i];
length = strlen(tmp) + 1;
- if ((length < 3) || (length > 255))
+ if ((length < 2) || (length > 255))
continue;
p_dev->prod_id[i] = kmalloc(sizeof(char) * length,
* won't work, this doesn't matter much at the moment: the driver core doesn't
* support it either.
*/
-static DECLARE_MUTEX(device_add_lock);
+static DEFINE_MUTEX(device_add_lock);
struct pcmcia_device * pcmcia_device_add(struct pcmcia_socket *s, unsigned int function)
{
- struct pcmcia_device *p_dev;
+ struct pcmcia_device *p_dev, *tmp_dev;
unsigned long flags;
int bus_id_len;
if (!s)
return NULL;
- down(&device_add_lock);
+ mutex_lock(&device_add_lock);
/* max of 2 devices per card */
if (s->device_count == 2)
p_dev->socket = s;
p_dev->device_no = (s->device_count++);
p_dev->func = function;
+ if (s->functions <= function)
+ s->functions = function + 1;
p_dev->dev.bus = &pcmcia_bus_type;
p_dev->dev.parent = s->dev.dev;
sprintf (p_dev->devname, "pcmcia%s", p_dev->dev.bus_id);
/* compat */
- p_dev->state = CLIENT_UNBOUND;
+ p_dev->p_state = CLIENT_UNBOUND;
+
- /* Add to the list in pcmcia_bus_socket */
spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+
+ /*
+ * p_dev->function_config must be the same for all card functions.
+ * Note that this is serialized by the device_add_lock, 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;
+ kref_get(&p_dev->function_config->ref);
+ }
+
+ /* Add to the list in pcmcia_bus_socket */
list_add_tail(&p_dev->socket_device_list, &s->devices_list);
+
spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+ if (!p_dev->function_config) {
+ p_dev->function_config = kzalloc(sizeof(struct config_t),
+ GFP_KERNEL);
+ if (!p_dev->function_config)
+ goto err_unreg;
+ kref_init(&p_dev->function_config->ref);
+ }
+
printk(KERN_NOTICE "pcmcia: registering new device %s\n",
p_dev->devname);
pcmcia_device_query(p_dev);
- if (device_register(&p_dev->dev)) {
- spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
- list_del(&p_dev->socket_device_list);
- spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
-
- goto err_free;
- }
+ if (device_register(&p_dev->dev))
+ goto err_unreg;
- up(&device_add_lock);
+ mutex_unlock(&device_add_lock);
return p_dev;
+ err_unreg:
+ spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
+ list_del(&p_dev->socket_device_list);
+ spin_unlock_irqrestore(&pcmcia_dev_list_lock, flags);
+
err_free:
kfree(p_dev->devname);
kfree(p_dev);
s->device_count--;
err_put:
- up(&device_add_lock);
+ mutex_unlock(&device_add_lock);
pcmcia_put_socket(s);
return NULL;
s->pcmcia_state.device_add_pending = 0;
}
-static inline void pcmcia_add_pseudo_device(struct pcmcia_socket *s)
-{
- if (!s->pcmcia_state.device_add_pending) {
- s->pcmcia_state.device_add_pending = 1;
- schedule_work(&s->device_add);
- }
- return;
-}
-
static int pcmcia_requery(struct device *dev, void * _data)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
int no_devices=0;
unsigned long flags;
- /* must be called with skt_sem held */
+ /* must be called with skt_mutex held */
spin_lock_irqsave(&pcmcia_dev_list_lock, flags);
if (list_empty(&skt->devices_list))
no_devices=1;
}
if (did->match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO) {
- /* handle pseudo multifunction devices:
- * there are at most two pseudo multifunction devices.
- * if we're matching against the first, schedule a
- * call which will then check whether there are two
- * pseudo devices, and if not, add the second one.
- */
- if (dev->device_no == 0)
- pcmcia_add_pseudo_device(dev->socket);
-
if (dev->device_no != did->device_no)
return 0;
}
struct pcmcia_driver * p_drv = to_pcmcia_drv(drv);
struct pcmcia_device_id *did = p_drv->id_table;
+#ifdef CONFIG_PCMCIA_IOCTL
/* matching by cardmgr */
if (p_dev->cardmgr == p_drv)
return 1;
+#endif
while (did && did->match_flags) {
if (pcmcia_devmatch(p_dev, did))
pcmcia_device_stringattr(prod_id3, prod_id[2]);
pcmcia_device_stringattr(prod_id4, prod_id[3]);
+
+static ssize_t pcmcia_show_pm_state(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
+
+ if (p_dev->dev.power.power_state.event != PM_EVENT_ON)
+ return sprintf(buf, "off\n");
+ else
+ return sprintf(buf, "on\n");
+}
+
+static ssize_t pcmcia_store_pm_state(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->dev.power.power_state.event == PM_EVENT_ON) &&
+ (!strncmp(buf, "off", 3)))
+ ret = dpm_runtime_suspend(dev, PMSG_SUSPEND);
+ else if ((p_dev->dev.power.power_state.event != PM_EVENT_ON) &&
+ (!strncmp(buf, "on", 2)))
+ dpm_runtime_resume(dev);
+
+ return ret ? ret : count;
+}
+
+
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
struct device_attribute *attr, const char *buf, size_t count)
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
- if (!count)
- return -EINVAL;
- down(&p_dev->socket->skt_sem);
+ if (!count)
+ return -EINVAL;
+
+ mutex_lock(&p_dev->socket->skt_mutex);
p_dev->allow_func_id_match = 1;
- up(&p_dev->socket->skt_sem);
+ mutex_unlock(&p_dev->socket->skt_mutex);
bus_rescan_devices(&pcmcia_bus_type);
static struct device_attribute pcmcia_dev_attrs[] = {
__ATTR(function, 0444, func_show, NULL),
+ __ATTR(pm_state, 0644, pcmcia_show_pm_state, pcmcia_store_pm_state),
__ATTR_RO(func_id),
__ATTR_RO(manf_id),
__ATTR_RO(card_id),
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
struct pcmcia_driver *p_drv = NULL;
+ int ret;
if (dev->driver)
p_drv = to_pcmcia_drv(dev->driver);
- if (p_drv && p_drv->suspend)
- return p_drv->suspend(p_dev);
+ if (p_drv && p_drv->suspend) {
+ ret = p_drv->suspend(p_dev);
+ if (ret)
+ return ret;
+ p_dev->state |= DEV_SUSPEND;
+ if ((p_dev->state & DEV_CONFIG) &&
+ !(p_dev->state & DEV_SUSPEND_NORELEASE))
+ pcmcia_release_configuration(p_dev);
+ }
return 0;
}
{
struct pcmcia_device *p_dev = to_pcmcia_dev(dev);
struct pcmcia_driver *p_drv = NULL;
+ int ret;
if (dev->driver)
p_drv = to_pcmcia_drv(dev->driver);
- if (p_drv && p_drv->resume)
+ if (p_drv && p_drv->resume) {
+ p_dev->state &= ~DEV_SUSPEND;
+ if ((p_dev->state & DEV_CONFIG) &&
+ !(p_dev->state & DEV_SUSPEND_NORELEASE)){
+ ret = pcmcia_request_configuration(p_dev,
+ &p_dev->conf);
+ if (ret)
+ return ret;
+ }
return p_drv->resume(p_dev);
+ }
return 0;
}
.uevent = pcmcia_bus_uevent,
.match = pcmcia_bus_match,
.dev_attrs = pcmcia_dev_attrs,
+ .probe = pcmcia_device_probe,
+ .remove = pcmcia_device_remove,
.suspend = pcmcia_dev_suspend,
.resume = pcmcia_dev_resume,
};