| /* | |
| * cpcihp_zt5550.c | |
| * | |
| * Intel/Ziatech ZT5550 CompactPCI Host Controller driver | |
| * | |
| * Copyright 2002 SOMA Networks, Inc. | |
| * Copyright 2001 Intel San Luis Obispo | |
| * Copyright 2000,2001 MontaVista Software Inc. | |
| * | |
| * This program is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License as published by the | |
| * Free Software Foundation; either version 2 of the License, or (at your | |
| * option) any later version. | |
| * | |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY | |
| * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL | |
| * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| * | |
| * You should have received a copy of the GNU General Public License along | |
| * with this program; if not, write to the Free Software Foundation, Inc., | |
| * 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| * Send feedback to <scottm@somanetworks.com> | |
| */ | |
| #include <linux/module.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/init.h> | |
| #include <linux/errno.h> | |
| #include <linux/pci.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/signal.h> /* IRQF_SHARED */ | |
| #include "cpci_hotplug.h" | |
| #include "cpcihp_zt5550.h" | |
| #define DRIVER_VERSION "0.2" | |
| #define DRIVER_AUTHOR "Scott Murray <scottm@somanetworks.com>" | |
| #define DRIVER_DESC "ZT5550 CompactPCI Hot Plug Driver" | |
| #define MY_NAME "cpcihp_zt5550" | |
| #define dbg(format, arg...) \ | |
| do { \ | |
| if (debug) \ | |
| printk(KERN_DEBUG "%s: " format "\n", \ | |
| MY_NAME, ## arg); \ | |
| } while (0) | |
| #define err(format, arg...) printk(KERN_ERR "%s: " format "\n", MY_NAME, ## arg) | |
| #define info(format, arg...) printk(KERN_INFO "%s: " format "\n", MY_NAME, ## arg) | |
| #define warn(format, arg...) printk(KERN_WARNING "%s: " format "\n", MY_NAME, ## arg) | |
| /* local variables */ | |
| static bool debug; | |
| static bool poll; | |
| static struct cpci_hp_controller_ops zt5550_hpc_ops; | |
| static struct cpci_hp_controller zt5550_hpc; | |
| /* Primary cPCI bus bridge device */ | |
| static struct pci_dev *bus0_dev; | |
| static struct pci_bus *bus0; | |
| /* Host controller device */ | |
| static struct pci_dev *hc_dev; | |
| /* Host controller register addresses */ | |
| static void __iomem *hc_registers; | |
| static void __iomem *csr_hc_index; | |
| static void __iomem *csr_hc_data; | |
| static void __iomem *csr_int_status; | |
| static void __iomem *csr_int_mask; | |
| static int zt5550_hc_config(struct pci_dev *pdev) | |
| { | |
| int ret; | |
| /* Since we know that no boards exist with two HC chips, treat it as an error */ | |
| if (hc_dev) { | |
| err("too many host controller devices?"); | |
| return -EBUSY; | |
| } | |
| ret = pci_enable_device(pdev); | |
| if (ret) { | |
| err("cannot enable %s\n", pci_name(pdev)); | |
| return ret; | |
| } | |
| hc_dev = pdev; | |
| dbg("hc_dev = %p", hc_dev); | |
| dbg("pci resource start %llx", (unsigned long long)pci_resource_start(hc_dev, 1)); | |
| dbg("pci resource len %llx", (unsigned long long)pci_resource_len(hc_dev, 1)); | |
| if (!request_mem_region(pci_resource_start(hc_dev, 1), | |
| pci_resource_len(hc_dev, 1), MY_NAME)) { | |
| err("cannot reserve MMIO region"); | |
| ret = -ENOMEM; | |
| goto exit_disable_device; | |
| } | |
| hc_registers = | |
| ioremap(pci_resource_start(hc_dev, 1), pci_resource_len(hc_dev, 1)); | |
| if (!hc_registers) { | |
| err("cannot remap MMIO region %llx @ %llx", | |
| (unsigned long long)pci_resource_len(hc_dev, 1), | |
| (unsigned long long)pci_resource_start(hc_dev, 1)); | |
| ret = -ENODEV; | |
| goto exit_release_region; | |
| } | |
| csr_hc_index = hc_registers + CSR_HCINDEX; | |
| csr_hc_data = hc_registers + CSR_HCDATA; | |
| csr_int_status = hc_registers + CSR_INTSTAT; | |
| csr_int_mask = hc_registers + CSR_INTMASK; | |
| /* | |
| * Disable host control, fault and serial interrupts | |
| */ | |
| dbg("disabling host control, fault and serial interrupts"); | |
| writeb((u8) HC_INT_MASK_REG, csr_hc_index); | |
| writeb((u8) ALL_INDEXED_INTS_MASK, csr_hc_data); | |
| dbg("disabled host control, fault and serial interrupts"); | |
| /* | |
| * Disable timer0, timer1 and ENUM interrupts | |
| */ | |
| dbg("disabling timer0, timer1 and ENUM interrupts"); | |
| writeb((u8) ALL_DIRECT_INTS_MASK, csr_int_mask); | |
| dbg("disabled timer0, timer1 and ENUM interrupts"); | |
| return 0; | |
| exit_release_region: | |
| release_mem_region(pci_resource_start(hc_dev, 1), | |
| pci_resource_len(hc_dev, 1)); | |
| exit_disable_device: | |
| pci_disable_device(hc_dev); | |
| return ret; | |
| } | |
| static int zt5550_hc_cleanup(void) | |
| { | |
| if (!hc_dev) | |
| return -ENODEV; | |
| iounmap(hc_registers); | |
| release_mem_region(pci_resource_start(hc_dev, 1), | |
| pci_resource_len(hc_dev, 1)); | |
| pci_disable_device(hc_dev); | |
| return 0; | |
| } | |
| static int zt5550_hc_query_enum(void) | |
| { | |
| u8 value; | |
| value = inb_p(ENUM_PORT); | |
| return ((value & ENUM_MASK) == ENUM_MASK); | |
| } | |
| static int zt5550_hc_check_irq(void *dev_id) | |
| { | |
| int ret; | |
| u8 reg; | |
| ret = 0; | |
| if (dev_id == zt5550_hpc.dev_id) { | |
| reg = readb(csr_int_status); | |
| if (reg) | |
| ret = 1; | |
| } | |
| return ret; | |
| } | |
| static int zt5550_hc_enable_irq(void) | |
| { | |
| u8 reg; | |
| if (hc_dev == NULL) | |
| return -ENODEV; | |
| reg = readb(csr_int_mask); | |
| reg = reg & ~ENUM_INT_MASK; | |
| writeb(reg, csr_int_mask); | |
| return 0; | |
| } | |
| static int zt5550_hc_disable_irq(void) | |
| { | |
| u8 reg; | |
| if (hc_dev == NULL) | |
| return -ENODEV; | |
| reg = readb(csr_int_mask); | |
| reg = reg | ENUM_INT_MASK; | |
| writeb(reg, csr_int_mask); | |
| return 0; | |
| } | |
| static int zt5550_hc_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) | |
| { | |
| int status; | |
| status = zt5550_hc_config(pdev); | |
| if (status != 0) | |
| return status; | |
| dbg("returned from zt5550_hc_config"); | |
| memset(&zt5550_hpc, 0, sizeof(struct cpci_hp_controller)); | |
| zt5550_hpc_ops.query_enum = zt5550_hc_query_enum; | |
| zt5550_hpc.ops = &zt5550_hpc_ops; | |
| if (!poll) { | |
| zt5550_hpc.irq = hc_dev->irq; | |
| zt5550_hpc.irq_flags = IRQF_SHARED; | |
| zt5550_hpc.dev_id = hc_dev; | |
| zt5550_hpc_ops.enable_irq = zt5550_hc_enable_irq; | |
| zt5550_hpc_ops.disable_irq = zt5550_hc_disable_irq; | |
| zt5550_hpc_ops.check_irq = zt5550_hc_check_irq; | |
| } else { | |
| info("using ENUM# polling mode"); | |
| } | |
| status = cpci_hp_register_controller(&zt5550_hpc); | |
| if (status != 0) { | |
| err("could not register cPCI hotplug controller"); | |
| goto init_hc_error; | |
| } | |
| dbg("registered controller"); | |
| /* Look for first device matching cPCI bus's bridge vendor and device IDs */ | |
| bus0_dev = pci_get_device(PCI_VENDOR_ID_DEC, | |
| PCI_DEVICE_ID_DEC_21154, NULL); | |
| if (!bus0_dev) { | |
| status = -ENODEV; | |
| goto init_register_error; | |
| } | |
| bus0 = bus0_dev->subordinate; | |
| pci_dev_put(bus0_dev); | |
| status = cpci_hp_register_bus(bus0, 0x0a, 0x0f); | |
| if (status != 0) { | |
| err("could not register cPCI hotplug bus"); | |
| goto init_register_error; | |
| } | |
| dbg("registered bus"); | |
| status = cpci_hp_start(); | |
| if (status != 0) { | |
| err("could not started cPCI hotplug system"); | |
| cpci_hp_unregister_bus(bus0); | |
| goto init_register_error; | |
| } | |
| dbg("started cpci hp system"); | |
| return 0; | |
| init_register_error: | |
| cpci_hp_unregister_controller(&zt5550_hpc); | |
| init_hc_error: | |
| err("status = %d", status); | |
| zt5550_hc_cleanup(); | |
| return status; | |
| } | |
| static void zt5550_hc_remove_one(struct pci_dev *pdev) | |
| { | |
| cpci_hp_stop(); | |
| cpci_hp_unregister_bus(bus0); | |
| cpci_hp_unregister_controller(&zt5550_hpc); | |
| zt5550_hc_cleanup(); | |
| } | |
| static struct pci_device_id zt5550_hc_pci_tbl[] = { | |
| { PCI_VENDOR_ID_ZIATECH, PCI_DEVICE_ID_ZIATECH_5550_HC, PCI_ANY_ID, PCI_ANY_ID, }, | |
| { 0, } | |
| }; | |
| MODULE_DEVICE_TABLE(pci, zt5550_hc_pci_tbl); | |
| static struct pci_driver zt5550_hc_driver = { | |
| .name = "zt5550_hc", | |
| .id_table = zt5550_hc_pci_tbl, | |
| .probe = zt5550_hc_init_one, | |
| .remove = zt5550_hc_remove_one, | |
| }; | |
| static int __init zt5550_init(void) | |
| { | |
| struct resource *r; | |
| int rc; | |
| info(DRIVER_DESC " version: " DRIVER_VERSION); | |
| r = request_region(ENUM_PORT, 1, "#ENUM hotswap signal register"); | |
| if (!r) | |
| return -EBUSY; | |
| rc = pci_register_driver(&zt5550_hc_driver); | |
| if (rc < 0) | |
| release_region(ENUM_PORT, 1); | |
| return rc; | |
| } | |
| static void __exit | |
| zt5550_exit(void) | |
| { | |
| pci_unregister_driver(&zt5550_hc_driver); | |
| release_region(ENUM_PORT, 1); | |
| } | |
| module_init(zt5550_init); | |
| module_exit(zt5550_exit); | |
| MODULE_AUTHOR(DRIVER_AUTHOR); | |
| MODULE_DESCRIPTION(DRIVER_DESC); | |
| MODULE_LICENSE("GPL"); | |
| module_param(debug, bool, 0644); | |
| MODULE_PARM_DESC(debug, "Debugging mode enabled or not"); | |
| module_param(poll, bool, 0644); | |
| MODULE_PARM_DESC(poll, "#ENUM polling mode enabled or not"); |