David Vrabel | 8f1b678 | 2008-09-17 16:34:13 +0100 | [diff] [blame] | 1 | /* |
| 2 | * WHCI UWB Multi-interface Controller enumerator. |
| 3 | * |
| 4 | * Copyright (C) 2007 Cambridge Silicon Radio Ltd. |
| 5 | * |
| 6 | * This file is released under the GNU GPL v2. |
| 7 | */ |
| 8 | #include <linux/delay.h> |
| 9 | #include <linux/kernel.h> |
| 10 | #include <linux/pci.h> |
| 11 | #include <linux/dma-mapping.h> |
| 12 | #include <linux/uwb/whci.h> |
| 13 | #include <linux/uwb/umc.h> |
| 14 | |
| 15 | struct whci_card { |
| 16 | struct pci_dev *pci; |
| 17 | void __iomem *uwbbase; |
| 18 | u8 n_caps; |
| 19 | struct umc_dev *devs[0]; |
| 20 | }; |
| 21 | |
| 22 | |
| 23 | /* Fix faulty HW :( */ |
| 24 | static |
| 25 | u64 whci_capdata_quirks(struct whci_card *card, u64 capdata) |
| 26 | { |
| 27 | u64 capdata_orig = capdata; |
| 28 | struct pci_dev *pci_dev = card->pci; |
| 29 | if (pci_dev->vendor == PCI_VENDOR_ID_INTEL |
| 30 | && (pci_dev->device == 0x0c3b || pci_dev->device == 0004) |
| 31 | && pci_dev->class == 0x0d1010) { |
| 32 | switch (UWBCAPDATA_TO_CAP_ID(capdata)) { |
| 33 | /* WLP capability has 0x100 bytes of aperture */ |
| 34 | case 0x80: |
| 35 | capdata |= 0x40 << 8; break; |
| 36 | /* WUSB capability has 0x80 bytes of aperture |
| 37 | * and ID is 1 */ |
| 38 | case 0x02: |
| 39 | capdata &= ~0xffff; |
| 40 | capdata |= 0x2001; |
| 41 | break; |
| 42 | } |
| 43 | } |
| 44 | if (capdata_orig != capdata) |
| 45 | dev_warn(&pci_dev->dev, |
| 46 | "PCI v%04x d%04x c%06x#%02x: " |
| 47 | "corrected capdata from %016Lx to %016Lx\n", |
| 48 | pci_dev->vendor, pci_dev->device, pci_dev->class, |
| 49 | (unsigned)UWBCAPDATA_TO_CAP_ID(capdata), |
| 50 | (unsigned long long)capdata_orig, |
| 51 | (unsigned long long)capdata); |
| 52 | return capdata; |
| 53 | } |
| 54 | |
| 55 | |
| 56 | /** |
| 57 | * whci_wait_for - wait for a WHCI register to be set |
| 58 | * |
| 59 | * Polls (for at most @max_ms ms) until '*@reg & @mask == @result'. |
| 60 | */ |
| 61 | int whci_wait_for(struct device *dev, u32 __iomem *reg, u32 mask, u32 result, |
| 62 | unsigned long max_ms, const char *tag) |
| 63 | { |
| 64 | unsigned t = 0; |
| 65 | u32 val; |
| 66 | for (;;) { |
| 67 | val = le_readl(reg); |
| 68 | if ((val & mask) == result) |
| 69 | break; |
David Vrabel | 8f1b678 | 2008-09-17 16:34:13 +0100 | [diff] [blame] | 70 | if (t >= max_ms) { |
David Vrabel | 5a4e1a7 | 2008-11-25 14:34:47 +0000 | [diff] [blame] | 71 | dev_err(dev, "%s timed out\n", tag); |
David Vrabel | 8f1b678 | 2008-09-17 16:34:13 +0100 | [diff] [blame] | 72 | return -ETIMEDOUT; |
| 73 | } |
David Vrabel | 5a4e1a7 | 2008-11-25 14:34:47 +0000 | [diff] [blame] | 74 | msleep(10); |
David Vrabel | 8f1b678 | 2008-09-17 16:34:13 +0100 | [diff] [blame] | 75 | t += 10; |
| 76 | } |
| 77 | return 0; |
| 78 | } |
| 79 | EXPORT_SYMBOL_GPL(whci_wait_for); |
| 80 | |
| 81 | |
| 82 | /* |
| 83 | * NOTE: the capinfo and capdata registers are slightly different |
| 84 | * (size and cap-id fields). So for cap #0, we need to fill |
| 85 | * in. Size comes from the size of the register block |
| 86 | * (statically calculated); cap_id comes from nowhere, we use |
| 87 | * zero, that is reserved, for the radio controller, because |
| 88 | * none was defined at the spec level. |
| 89 | */ |
| 90 | static int whci_add_cap(struct whci_card *card, int n) |
| 91 | { |
| 92 | struct umc_dev *umc; |
| 93 | u64 capdata; |
| 94 | int bar, err; |
| 95 | |
| 96 | umc = umc_device_create(&card->pci->dev, n); |
| 97 | if (umc == NULL) |
| 98 | return -ENOMEM; |
| 99 | |
| 100 | capdata = le_readq(card->uwbbase + UWBCAPDATA(n)); |
| 101 | |
| 102 | bar = UWBCAPDATA_TO_BAR(capdata) << 1; |
| 103 | |
| 104 | capdata = whci_capdata_quirks(card, capdata); |
| 105 | /* Capability 0 is the radio controller. It's size is 32 |
| 106 | * bytes (WHCI0.95[2.3, T2-9]). */ |
| 107 | umc->version = UWBCAPDATA_TO_VERSION(capdata); |
| 108 | umc->cap_id = n == 0 ? 0 : UWBCAPDATA_TO_CAP_ID(capdata); |
| 109 | umc->bar = bar; |
| 110 | umc->resource.start = pci_resource_start(card->pci, bar) |
| 111 | + UWBCAPDATA_TO_OFFSET(capdata); |
| 112 | umc->resource.end = umc->resource.start |
| 113 | + (n == 0 ? 0x20 : UWBCAPDATA_TO_SIZE(capdata)) - 1; |
Kay Sievers | ae9eba0 | 2008-10-30 20:06:16 +0100 | [diff] [blame] | 114 | umc->resource.name = dev_name(&umc->dev); |
David Vrabel | 8f1b678 | 2008-09-17 16:34:13 +0100 | [diff] [blame] | 115 | umc->resource.flags = card->pci->resource[bar].flags; |
| 116 | umc->resource.parent = &card->pci->resource[bar]; |
| 117 | umc->irq = card->pci->irq; |
| 118 | |
| 119 | err = umc_device_register(umc); |
| 120 | if (err < 0) |
| 121 | goto error; |
| 122 | card->devs[n] = umc; |
| 123 | return 0; |
| 124 | |
| 125 | error: |
| 126 | kfree(umc); |
| 127 | return err; |
| 128 | } |
| 129 | |
| 130 | static void whci_del_cap(struct whci_card *card, int n) |
| 131 | { |
| 132 | struct umc_dev *umc = card->devs[n]; |
| 133 | |
| 134 | if (umc != NULL) |
| 135 | umc_device_unregister(umc); |
| 136 | } |
| 137 | |
| 138 | static int whci_n_caps(struct pci_dev *pci) |
| 139 | { |
| 140 | void __iomem *uwbbase; |
| 141 | u64 capinfo; |
| 142 | |
| 143 | uwbbase = pci_iomap(pci, 0, 8); |
| 144 | if (!uwbbase) |
| 145 | return -ENOMEM; |
| 146 | capinfo = le_readq(uwbbase + UWBCAPINFO); |
| 147 | pci_iounmap(pci, uwbbase); |
| 148 | |
| 149 | return UWBCAPINFO_TO_N_CAPS(capinfo); |
| 150 | } |
| 151 | |
| 152 | static int whci_probe(struct pci_dev *pci, const struct pci_device_id *id) |
| 153 | { |
| 154 | struct whci_card *card; |
| 155 | int err, n_caps, n; |
| 156 | |
| 157 | err = pci_enable_device(pci); |
| 158 | if (err < 0) |
| 159 | goto error; |
| 160 | pci_enable_msi(pci); |
| 161 | pci_set_master(pci); |
| 162 | err = -ENXIO; |
| 163 | if (!pci_set_dma_mask(pci, DMA_64BIT_MASK)) |
| 164 | pci_set_consistent_dma_mask(pci, DMA_64BIT_MASK); |
| 165 | else if (!pci_set_dma_mask(pci, DMA_32BIT_MASK)) |
| 166 | pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK); |
| 167 | else |
| 168 | goto error_dma; |
| 169 | |
| 170 | err = n_caps = whci_n_caps(pci); |
| 171 | if (n_caps < 0) |
| 172 | goto error_ncaps; |
| 173 | |
| 174 | err = -ENOMEM; |
| 175 | card = kzalloc(sizeof(struct whci_card) |
| 176 | + sizeof(struct whci_dev *) * (n_caps + 1), |
| 177 | GFP_KERNEL); |
| 178 | if (card == NULL) |
| 179 | goto error_kzalloc; |
| 180 | card->pci = pci; |
| 181 | card->n_caps = n_caps; |
| 182 | |
| 183 | err = -EBUSY; |
| 184 | if (!request_mem_region(pci_resource_start(pci, 0), |
| 185 | UWBCAPDATA_SIZE(card->n_caps), |
| 186 | "whci (capability data)")) |
| 187 | goto error_request_memregion; |
| 188 | err = -ENOMEM; |
| 189 | card->uwbbase = pci_iomap(pci, 0, UWBCAPDATA_SIZE(card->n_caps)); |
| 190 | if (!card->uwbbase) |
| 191 | goto error_iomap; |
| 192 | |
| 193 | /* Add each capability. */ |
| 194 | for (n = 0; n <= card->n_caps; n++) { |
| 195 | err = whci_add_cap(card, n); |
| 196 | if (err < 0 && n == 0) { |
| 197 | dev_err(&pci->dev, "cannot bind UWB radio controller:" |
| 198 | " %d\n", err); |
| 199 | goto error_bind; |
| 200 | } |
| 201 | if (err < 0) |
| 202 | dev_warn(&pci->dev, "warning: cannot bind capability " |
| 203 | "#%u: %d\n", n, err); |
| 204 | } |
| 205 | pci_set_drvdata(pci, card); |
| 206 | return 0; |
| 207 | |
| 208 | error_bind: |
| 209 | pci_iounmap(pci, card->uwbbase); |
| 210 | error_iomap: |
| 211 | release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); |
| 212 | error_request_memregion: |
| 213 | kfree(card); |
| 214 | error_kzalloc: |
| 215 | error_ncaps: |
| 216 | error_dma: |
| 217 | pci_disable_msi(pci); |
| 218 | pci_disable_device(pci); |
| 219 | error: |
| 220 | return err; |
| 221 | } |
| 222 | |
| 223 | static void whci_remove(struct pci_dev *pci) |
| 224 | { |
| 225 | struct whci_card *card = pci_get_drvdata(pci); |
| 226 | int n; |
| 227 | |
| 228 | pci_set_drvdata(pci, NULL); |
| 229 | /* Unregister each capability in reverse (so the master device |
| 230 | * is unregistered last). */ |
| 231 | for (n = card->n_caps; n >= 0 ; n--) |
| 232 | whci_del_cap(card, n); |
| 233 | pci_iounmap(pci, card->uwbbase); |
| 234 | release_mem_region(pci_resource_start(pci, 0), UWBCAPDATA_SIZE(card->n_caps)); |
| 235 | kfree(card); |
| 236 | pci_disable_msi(pci); |
| 237 | pci_disable_device(pci); |
| 238 | } |
| 239 | |
| 240 | static struct pci_device_id whci_id_table[] = { |
| 241 | { PCI_DEVICE_CLASS(PCI_CLASS_WIRELESS_WHCI, ~0) }, |
| 242 | { 0 }, |
| 243 | }; |
| 244 | MODULE_DEVICE_TABLE(pci, whci_id_table); |
| 245 | |
| 246 | |
| 247 | static struct pci_driver whci_driver = { |
| 248 | .name = "whci", |
| 249 | .id_table = whci_id_table, |
| 250 | .probe = whci_probe, |
| 251 | .remove = whci_remove, |
| 252 | }; |
| 253 | |
| 254 | static int __init whci_init(void) |
| 255 | { |
| 256 | return pci_register_driver(&whci_driver); |
| 257 | } |
| 258 | |
| 259 | static void __exit whci_exit(void) |
| 260 | { |
| 261 | pci_unregister_driver(&whci_driver); |
| 262 | } |
| 263 | |
| 264 | module_init(whci_init); |
| 265 | module_exit(whci_exit); |
| 266 | |
| 267 | MODULE_DESCRIPTION("WHCI UWB Multi-interface Controller enumerator"); |
| 268 | MODULE_AUTHOR("Cambridge Silicon Radio Ltd."); |
| 269 | MODULE_LICENSE("GPL"); |