| /* | 
 |  * Broadcom 43xx PCMCIA-SSB bridge module | 
 |  * | 
 |  * Copyright (c) 2007 Michael Buesch <m@bues.ch> | 
 |  * | 
 |  * Licensed under the GNU/GPL. See COPYING for details. | 
 |  */ | 
 |  | 
 | #include "ssb_private.h" | 
 |  | 
 | #include <linux/ssb/ssb.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/module.h> | 
 |  | 
 | #include <pcmcia/cistpl.h> | 
 | #include <pcmcia/ciscode.h> | 
 | #include <pcmcia/ds.h> | 
 | #include <pcmcia/cisreg.h> | 
 |  | 
 | static const struct pcmcia_device_id ssb_host_pcmcia_tbl[] = { | 
 | 	PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x448), | 
 | 	PCMCIA_DEVICE_MANF_CARD(0x2D0, 0x476), | 
 | 	PCMCIA_DEVICE_NULL, | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(pcmcia, ssb_host_pcmcia_tbl); | 
 |  | 
 | static int ssb_host_pcmcia_probe(struct pcmcia_device *dev) | 
 | { | 
 | 	struct ssb_bus *ssb; | 
 | 	int err = -ENOMEM; | 
 | 	int res = 0; | 
 |  | 
 | 	ssb = kzalloc(sizeof(*ssb), GFP_KERNEL); | 
 | 	if (!ssb) | 
 | 		goto out_error; | 
 |  | 
 | 	err = -ENODEV; | 
 |  | 
 | 	dev->config_flags |= CONF_ENABLE_IRQ; | 
 |  | 
 | 	dev->resource[2]->flags |=  WIN_ENABLE | WIN_DATA_WIDTH_16 | | 
 | 			 WIN_USE_WAIT; | 
 | 	dev->resource[2]->start = 0; | 
 | 	dev->resource[2]->end = SSB_CORE_SIZE; | 
 | 	res = pcmcia_request_window(dev, dev->resource[2], 250); | 
 | 	if (res != 0) | 
 | 		goto err_kfree_ssb; | 
 |  | 
 | 	res = pcmcia_map_mem_page(dev, dev->resource[2], 0); | 
 | 	if (res != 0) | 
 | 		goto err_disable; | 
 |  | 
 | 	if (!dev->irq) | 
 | 		goto err_disable; | 
 |  | 
 | 	res = pcmcia_enable_device(dev); | 
 | 	if (res != 0) | 
 | 		goto err_disable; | 
 |  | 
 | 	err = ssb_bus_pcmciabus_register(ssb, dev, dev->resource[2]->start); | 
 | 	if (err) | 
 | 		goto err_disable; | 
 | 	dev->priv = ssb; | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_disable: | 
 | 	pcmcia_disable_device(dev); | 
 | err_kfree_ssb: | 
 | 	kfree(ssb); | 
 | out_error: | 
 | 	dev_err(&dev->dev, "Initialization failed (%d, %d)\n", res, err); | 
 | 	return err; | 
 | } | 
 |  | 
 | static void ssb_host_pcmcia_remove(struct pcmcia_device *dev) | 
 | { | 
 | 	struct ssb_bus *ssb = dev->priv; | 
 |  | 
 | 	ssb_bus_unregister(ssb); | 
 | 	pcmcia_disable_device(dev); | 
 | 	kfree(ssb); | 
 | 	dev->priv = NULL; | 
 | } | 
 |  | 
 | #ifdef CONFIG_PM | 
 | static int ssb_host_pcmcia_suspend(struct pcmcia_device *dev) | 
 | { | 
 | 	struct ssb_bus *ssb = dev->priv; | 
 |  | 
 | 	return ssb_bus_suspend(ssb); | 
 | } | 
 |  | 
 | static int ssb_host_pcmcia_resume(struct pcmcia_device *dev) | 
 | { | 
 | 	struct ssb_bus *ssb = dev->priv; | 
 |  | 
 | 	return ssb_bus_resume(ssb); | 
 | } | 
 | #else /* CONFIG_PM */ | 
 | # define ssb_host_pcmcia_suspend		NULL | 
 | # define ssb_host_pcmcia_resume		NULL | 
 | #endif /* CONFIG_PM */ | 
 |  | 
 | static struct pcmcia_driver ssb_host_pcmcia_driver = { | 
 | 	.owner		= THIS_MODULE, | 
 | 	.name		= "ssb-pcmcia", | 
 | 	.id_table	= ssb_host_pcmcia_tbl, | 
 | 	.probe		= ssb_host_pcmcia_probe, | 
 | 	.remove		= ssb_host_pcmcia_remove, | 
 | 	.suspend	= ssb_host_pcmcia_suspend, | 
 | 	.resume		= ssb_host_pcmcia_resume, | 
 | }; | 
 |  | 
 | static int pcmcia_init_failed; | 
 |  | 
 | /* | 
 |  * These are not module init/exit functions! | 
 |  * The module_pcmcia_driver() helper cannot be used here. | 
 |  */ | 
 | int ssb_host_pcmcia_init(void) | 
 | { | 
 | 	pcmcia_init_failed = pcmcia_register_driver(&ssb_host_pcmcia_driver); | 
 |  | 
 | 	return pcmcia_init_failed; | 
 | } | 
 |  | 
 | void ssb_host_pcmcia_exit(void) | 
 | { | 
 | 	if (!pcmcia_init_failed) | 
 | 		pcmcia_unregister_driver(&ssb_host_pcmcia_driver); | 
 | } |