/* * PTX5000 SIB - PCI fixup code * * Rajat Jain * Copyright 2014 Juniper Networks * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License v2 as published by the * Free Software Foundation */ #include #include #include #include #define IDT_PES12NT3_DLSTS 0x268 #define IDT_PES12NT3_DLSTS_DLFSM 0x7 #define IDT_PES12NT3_DLSTS_LINKACTIVE 0x4 struct pes12nt3_pci_data { struct list_head list; struct device *dev; struct pci_ops ops; struct pci_ops *old_ops; bool lnksta_dllla; bool sltsta_dllsc; }; static LIST_HEAD(pes12nt3_list); static DEFINE_SPINLOCK(pes12nt3_lock); static int pes12nt3_update_linkstatus(struct pes12nt3_pci_data *data, struct pci_bus *bus, unsigned int devfn) { u32 linkactive; bool dllla; int retval; retval = data->old_ops->read(bus, devfn, IDT_PES12NT3_DLSTS, 4, &linkactive); if (retval) return retval; if (linkactive == 0xffffffff) dllla = false; else dllla = (linkactive & IDT_PES12NT3_DLSTS_DLFSM) == IDT_PES12NT3_DLSTS_LINKACTIVE; if (dllla != data->lnksta_dllla) data->sltsta_dllsc = true; data->lnksta_dllla = dllla; return 0; } static int pes12nt3_pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *val) { struct pes12nt3_pci_data *data = container_of(bus->ops, struct pes12nt3_pci_data, ops); int retval, pos; retval = data->old_ops->read(bus, devfn, where, size, val); /* Only need to change the registers at port leading to TF chip */ if (retval || devfn != 0) return retval; retval = pes12nt3_update_linkstatus(data, bus, devfn); if (retval) return retval; pos = where - 0x40; /* * PCI registers smaller than 32 bits may be read using * different lengths at diferent offsets. Consider: * * +-----------------------------------+ * | Reg-1 | Reg-2 | Reg-3 | Reg-4 | * +-----------------------------------+ * ^ ^ ^ ^ * ptr ptr+1 ptr+2 ptr+3 * * Reg-4 may be read by using a: * 1 byte read at (ptr+3) * 2 byte read at (ptr+2) * 4 byte read at (ptr) * etc * * We need to take of this here. */ switch (size) { case 4: switch (pos) { case 0: if (*val == 0xffffffff) *val = 0; else *val |= (PCI_EXP_FLAGS_SLOT << 16); break; case PCI_EXP_LNKCAP: if (*val == 0xffffffff) *val = 0; else *val |= PCI_EXP_LNKCAP_DLLLARC; break; case PCI_EXP_SLTCAP: if (*val == 0xffffffff) { *val = 0; } else { *val |= PCI_EXP_SLTCAP_HPC; *val = (*val & ~PCI_EXP_SLTCAP_PSN) | (bus->number << 19); } break; case PCI_EXP_LNKCTL: if (*val == 0xffffffff) { *val = 0; } else { if (data->lnksta_dllla) *val |= PCI_EXP_LNKSTA_DLLLA << 16; else *val &= ~(PCI_EXP_LNKSTA_DLLLA << 16); } break; } break; case 2: switch (pos) { case PCI_EXP_FLAGS_SLOT: if (*val == 0xffff) *val = 0; else *val |= PCI_EXP_FLAGS_SLOT; break; case PCI_EXP_LNKSTA: if (*val == 0xffff) { *val = 0; } else { if (data->lnksta_dllla) *val |= PCI_EXP_LNKSTA_DLLLA; else *val &= ~PCI_EXP_LNKSTA_DLLLA; } break; case PCI_EXP_SLTSTA: if (*val == 0xffff) *val = PCI_EXP_SLTSTA_DLLSC; else if (data->sltsta_dllsc) *val |= PCI_EXP_SLTSTA_DLLSC; break; } break; } return 0; } static int pes12nt3_pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 val) { struct pes12nt3_pci_data *data = container_of(bus->ops, struct pes12nt3_pci_data, ops); int pos = where - 0x40; int retval; if (devfn == 0 && size == 2) { switch (pos) { case PCI_EXP_SLTSTA: if (val & PCI_EXP_SLTSTA_DLLSC) data->sltsta_dllsc = false; break; } } retval = data->old_ops->write(bus, devfn, where, size, val); if (retval || devfn != 0) return retval; /* Catch situations where the link status changed after being handled */ return pes12nt3_update_linkstatus(data, bus, devfn); } static void pes12nt3_fake_linkstate_hotplug(struct pci_dev *dev) { struct pes12nt3_pci_data *data; if (pci_pcie_type(dev) == PCI_EXP_TYPE_UPSTREAM) { data = kzalloc(sizeof(*data), GFP_KERNEL); if (data == NULL) return; data->ops.read = pes12nt3_pci_read; data->ops.write = pes12nt3_pci_write; data->old_ops = pci_bus_set_ops(dev->subordinate, &data->ops); data->dev = &dev->dev; spin_lock(&pes12nt3_lock); list_add(&data->list, &pes12nt3_list); spin_unlock(&pes12nt3_lock); } else if (pci_pcie_type(dev) == PCI_EXP_TYPE_DOWNSTREAM) { dev->hotplug_polling = 1; /* No IRQ support */ dev->pcie_flags_reg |= PCI_EXP_FLAGS_SLOT; } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_IDT, PCI_DEVICE_ID_IDT_PES12NT3_TRANS_AB, pes12nt3_fake_linkstate_hotplug); static void pes12nt3_cleanup_entry(struct device *dev) { struct pes12nt3_pci_data *data, *tmp; spin_lock(&pes12nt3_lock); list_for_each_entry_safe(data, tmp, &pes12nt3_list, list) { if (data->dev == dev) { list_del(&data->list); kfree(data); break; } } spin_unlock(&pes12nt3_lock); } static int pes12nt3_notifier_call(struct notifier_block *n, unsigned long action, void *dev) { if (action == BUS_NOTIFY_DEL_DEVICE) pes12nt3_cleanup_entry(dev); return NOTIFY_DONE; } static struct notifier_block pes12nt3_nb = { .notifier_call = pes12nt3_notifier_call, }; static int __init pes12nt3_init(void) { bus_register_notifier(&pci_bus_type, &pes12nt3_nb); return 0; } subsys_initcall(pes12nt3_init);