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