| /* |
| * 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); |
| } |