diff options
Diffstat (limited to 'drivers/scsi/pm8001/pm8001_init.c')
-rw-r--r-- | drivers/scsi/pm8001/pm8001_init.c | 383 |
1 files changed, 295 insertions, 88 deletions
diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index 3d5e522e00fc..e4b9bc7f5410 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * PMC-Sierra SPC 8001 SAS/SATA based host adapters driver | 2 | * PMC-Sierra PM8001/8081/8088/8089 SAS/SATA based host adapters driver |
3 | * | 3 | * |
4 | * Copyright (c) 2008-2009 USI Co., Ltd. | 4 | * Copyright (c) 2008-2009 USI Co., Ltd. |
5 | * All rights reserved. | 5 | * All rights reserved. |
@@ -44,8 +44,16 @@ | |||
44 | 44 | ||
45 | static struct scsi_transport_template *pm8001_stt; | 45 | static struct scsi_transport_template *pm8001_stt; |
46 | 46 | ||
47 | /** | ||
48 | * chip info structure to identify chip key functionality as | ||
49 | * encryption available/not, no of ports, hw specific function ref | ||
50 | */ | ||
47 | static const struct pm8001_chip_info pm8001_chips[] = { | 51 | static const struct pm8001_chip_info pm8001_chips[] = { |
48 | [chip_8001] = { 8, &pm8001_8001_dispatch,}, | 52 | [chip_8001] = {0, 8, &pm8001_8001_dispatch,}, |
53 | [chip_8008] = {0, 8, &pm8001_80xx_dispatch,}, | ||
54 | [chip_8009] = {1, 8, &pm8001_80xx_dispatch,}, | ||
55 | [chip_8018] = {0, 16, &pm8001_80xx_dispatch,}, | ||
56 | [chip_8019] = {1, 16, &pm8001_80xx_dispatch,}, | ||
49 | }; | 57 | }; |
50 | static int pm8001_id; | 58 | static int pm8001_id; |
51 | 59 | ||
@@ -155,37 +163,75 @@ static void pm8001_free(struct pm8001_hba_info *pm8001_ha) | |||
155 | } | 163 | } |
156 | 164 | ||
157 | #ifdef PM8001_USE_TASKLET | 165 | #ifdef PM8001_USE_TASKLET |
166 | |||
167 | /** | ||
168 | * tasklet for 64 msi-x interrupt handler | ||
169 | * @opaque: the passed general host adapter struct | ||
170 | * Note: pm8001_tasklet is common for pm8001 & pm80xx | ||
171 | */ | ||
158 | static void pm8001_tasklet(unsigned long opaque) | 172 | static void pm8001_tasklet(unsigned long opaque) |
159 | { | 173 | { |
160 | struct pm8001_hba_info *pm8001_ha; | 174 | struct pm8001_hba_info *pm8001_ha; |
175 | u32 vec; | ||
161 | pm8001_ha = (struct pm8001_hba_info *)opaque; | 176 | pm8001_ha = (struct pm8001_hba_info *)opaque; |
162 | if (unlikely(!pm8001_ha)) | 177 | if (unlikely(!pm8001_ha)) |
163 | BUG_ON(1); | 178 | BUG_ON(1); |
164 | PM8001_CHIP_DISP->isr(pm8001_ha); | 179 | vec = pm8001_ha->int_vector; |
180 | PM8001_CHIP_DISP->isr(pm8001_ha, vec); | ||
181 | } | ||
182 | #endif | ||
183 | |||
184 | static struct pm8001_hba_info *outq_to_hba(u8 *outq) | ||
185 | { | ||
186 | return container_of((outq - *outq), struct pm8001_hba_info, outq[0]); | ||
165 | } | 187 | } |
188 | |||
189 | /** | ||
190 | * pm8001_interrupt_handler_msix - main MSIX interrupt handler. | ||
191 | * It obtains the vector number and calls the equivalent bottom | ||
192 | * half or services directly. | ||
193 | * @opaque: the passed outbound queue/vector. Host structure is | ||
194 | * retrieved from the same. | ||
195 | */ | ||
196 | static irqreturn_t pm8001_interrupt_handler_msix(int irq, void *opaque) | ||
197 | { | ||
198 | struct pm8001_hba_info *pm8001_ha = outq_to_hba(opaque); | ||
199 | u8 outq = *(u8 *)opaque; | ||
200 | irqreturn_t ret = IRQ_HANDLED; | ||
201 | if (unlikely(!pm8001_ha)) | ||
202 | return IRQ_NONE; | ||
203 | if (!PM8001_CHIP_DISP->is_our_interupt(pm8001_ha)) | ||
204 | return IRQ_NONE; | ||
205 | pm8001_ha->int_vector = outq; | ||
206 | #ifdef PM8001_USE_TASKLET | ||
207 | tasklet_schedule(&pm8001_ha->tasklet); | ||
208 | #else | ||
209 | ret = PM8001_CHIP_DISP->isr(pm8001_ha, outq); | ||
166 | #endif | 210 | #endif |
211 | return ret; | ||
212 | } | ||
167 | 213 | ||
214 | /** | ||
215 | * pm8001_interrupt_handler_intx - main INTx interrupt handler. | ||
216 | * @dev_id: sas_ha structure. The HBA is retrieved from sas_has structure. | ||
217 | */ | ||
168 | 218 | ||
169 | /** | 219 | static irqreturn_t pm8001_interrupt_handler_intx(int irq, void *dev_id) |
170 | * pm8001_interrupt - when HBA originate a interrupt,we should invoke this | ||
171 | * dispatcher to handle each case. | ||
172 | * @irq: irq number. | ||
173 | * @opaque: the passed general host adapter struct | ||
174 | */ | ||
175 | static irqreturn_t pm8001_interrupt(int irq, void *opaque) | ||
176 | { | 220 | { |
177 | struct pm8001_hba_info *pm8001_ha; | 221 | struct pm8001_hba_info *pm8001_ha; |
178 | irqreturn_t ret = IRQ_HANDLED; | 222 | irqreturn_t ret = IRQ_HANDLED; |
179 | struct sas_ha_struct *sha = opaque; | 223 | struct sas_ha_struct *sha = dev_id; |
180 | pm8001_ha = sha->lldd_ha; | 224 | pm8001_ha = sha->lldd_ha; |
181 | if (unlikely(!pm8001_ha)) | 225 | if (unlikely(!pm8001_ha)) |
182 | return IRQ_NONE; | 226 | return IRQ_NONE; |
183 | if (!PM8001_CHIP_DISP->is_our_interupt(pm8001_ha)) | 227 | if (!PM8001_CHIP_DISP->is_our_interupt(pm8001_ha)) |
184 | return IRQ_NONE; | 228 | return IRQ_NONE; |
229 | |||
230 | pm8001_ha->int_vector = 0; | ||
185 | #ifdef PM8001_USE_TASKLET | 231 | #ifdef PM8001_USE_TASKLET |
186 | tasklet_schedule(&pm8001_ha->tasklet); | 232 | tasklet_schedule(&pm8001_ha->tasklet); |
187 | #else | 233 | #else |
188 | ret = PM8001_CHIP_DISP->isr(pm8001_ha); | 234 | ret = PM8001_CHIP_DISP->isr(pm8001_ha, 0); |
189 | #endif | 235 | #endif |
190 | return ret; | 236 | return ret; |
191 | } | 237 | } |
@@ -195,10 +241,14 @@ static irqreturn_t pm8001_interrupt(int irq, void *opaque) | |||
195 | * @pm8001_ha:our hba structure. | 241 | * @pm8001_ha:our hba structure. |
196 | * | 242 | * |
197 | */ | 243 | */ |
198 | static int pm8001_alloc(struct pm8001_hba_info *pm8001_ha) | 244 | static int pm8001_alloc(struct pm8001_hba_info *pm8001_ha, |
245 | const struct pci_device_id *ent) | ||
199 | { | 246 | { |
200 | int i; | 247 | int i; |
201 | spin_lock_init(&pm8001_ha->lock); | 248 | spin_lock_init(&pm8001_ha->lock); |
249 | PM8001_INIT_DBG(pm8001_ha, | ||
250 | pm8001_printk("pm8001_alloc: PHY:%x\n", | ||
251 | pm8001_ha->chip->n_phy)); | ||
202 | for (i = 0; i < pm8001_ha->chip->n_phy; i++) { | 252 | for (i = 0; i < pm8001_ha->chip->n_phy; i++) { |
203 | pm8001_phy_init(pm8001_ha, i); | 253 | pm8001_phy_init(pm8001_ha, i); |
204 | pm8001_ha->port[i].wide_port_phymap = 0; | 254 | pm8001_ha->port[i].wide_port_phymap = 0; |
@@ -222,30 +272,57 @@ static int pm8001_alloc(struct pm8001_hba_info *pm8001_ha) | |||
222 | pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; | 272 | pm8001_ha->memoryMap.region[IOP].total_len = PM8001_EVENT_LOG_SIZE; |
223 | pm8001_ha->memoryMap.region[IOP].alignment = 32; | 273 | pm8001_ha->memoryMap.region[IOP].alignment = 32; |
224 | 274 | ||
225 | /* MPI Memory region 3 for consumer Index of inbound queues */ | 275 | for (i = 0; i < PM8001_MAX_SPCV_INB_NUM; i++) { |
226 | pm8001_ha->memoryMap.region[CI].num_elements = 1; | 276 | /* MPI Memory region 3 for consumer Index of inbound queues */ |
227 | pm8001_ha->memoryMap.region[CI].element_size = 4; | 277 | pm8001_ha->memoryMap.region[CI+i].num_elements = 1; |
228 | pm8001_ha->memoryMap.region[CI].total_len = 4; | 278 | pm8001_ha->memoryMap.region[CI+i].element_size = 4; |
229 | pm8001_ha->memoryMap.region[CI].alignment = 4; | 279 | pm8001_ha->memoryMap.region[CI+i].total_len = 4; |
230 | 280 | pm8001_ha->memoryMap.region[CI+i].alignment = 4; | |
231 | /* MPI Memory region 4 for producer Index of outbound queues */ | 281 | |
232 | pm8001_ha->memoryMap.region[PI].num_elements = 1; | 282 | if ((ent->driver_data) != chip_8001) { |
233 | pm8001_ha->memoryMap.region[PI].element_size = 4; | 283 | /* MPI Memory region 5 inbound queues */ |
234 | pm8001_ha->memoryMap.region[PI].total_len = 4; | 284 | pm8001_ha->memoryMap.region[IB+i].num_elements = |
235 | pm8001_ha->memoryMap.region[PI].alignment = 4; | 285 | PM8001_MPI_QUEUE; |
236 | 286 | pm8001_ha->memoryMap.region[IB+i].element_size = 128; | |
237 | /* MPI Memory region 5 inbound queues */ | 287 | pm8001_ha->memoryMap.region[IB+i].total_len = |
238 | pm8001_ha->memoryMap.region[IB].num_elements = PM8001_MPI_QUEUE; | 288 | PM8001_MPI_QUEUE * 128; |
239 | pm8001_ha->memoryMap.region[IB].element_size = 64; | 289 | pm8001_ha->memoryMap.region[IB+i].alignment = 128; |
240 | pm8001_ha->memoryMap.region[IB].total_len = PM8001_MPI_QUEUE * 64; | 290 | } else { |
241 | pm8001_ha->memoryMap.region[IB].alignment = 64; | 291 | pm8001_ha->memoryMap.region[IB+i].num_elements = |
242 | 292 | PM8001_MPI_QUEUE; | |
243 | /* MPI Memory region 6 outbound queues */ | 293 | pm8001_ha->memoryMap.region[IB+i].element_size = 64; |
244 | pm8001_ha->memoryMap.region[OB].num_elements = PM8001_MPI_QUEUE; | 294 | pm8001_ha->memoryMap.region[IB+i].total_len = |
245 | pm8001_ha->memoryMap.region[OB].element_size = 64; | 295 | PM8001_MPI_QUEUE * 64; |
246 | pm8001_ha->memoryMap.region[OB].total_len = PM8001_MPI_QUEUE * 64; | 296 | pm8001_ha->memoryMap.region[IB+i].alignment = 64; |
247 | pm8001_ha->memoryMap.region[OB].alignment = 64; | 297 | } |
298 | } | ||
299 | |||
300 | for (i = 0; i < PM8001_MAX_SPCV_OUTB_NUM; i++) { | ||
301 | /* MPI Memory region 4 for producer Index of outbound queues */ | ||
302 | pm8001_ha->memoryMap.region[PI+i].num_elements = 1; | ||
303 | pm8001_ha->memoryMap.region[PI+i].element_size = 4; | ||
304 | pm8001_ha->memoryMap.region[PI+i].total_len = 4; | ||
305 | pm8001_ha->memoryMap.region[PI+i].alignment = 4; | ||
306 | |||
307 | if (ent->driver_data != chip_8001) { | ||
308 | /* MPI Memory region 6 Outbound queues */ | ||
309 | pm8001_ha->memoryMap.region[OB+i].num_elements = | ||
310 | PM8001_MPI_QUEUE; | ||
311 | pm8001_ha->memoryMap.region[OB+i].element_size = 128; | ||
312 | pm8001_ha->memoryMap.region[OB+i].total_len = | ||
313 | PM8001_MPI_QUEUE * 128; | ||
314 | pm8001_ha->memoryMap.region[OB+i].alignment = 128; | ||
315 | } else { | ||
316 | /* MPI Memory region 6 Outbound queues */ | ||
317 | pm8001_ha->memoryMap.region[OB+i].num_elements = | ||
318 | PM8001_MPI_QUEUE; | ||
319 | pm8001_ha->memoryMap.region[OB+i].element_size = 64; | ||
320 | pm8001_ha->memoryMap.region[OB+i].total_len = | ||
321 | PM8001_MPI_QUEUE * 64; | ||
322 | pm8001_ha->memoryMap.region[OB+i].alignment = 64; | ||
323 | } | ||
248 | 324 | ||
325 | } | ||
249 | /* Memory region write DMA*/ | 326 | /* Memory region write DMA*/ |
250 | pm8001_ha->memoryMap.region[NVMD].num_elements = 1; | 327 | pm8001_ha->memoryMap.region[NVMD].num_elements = 1; |
251 | pm8001_ha->memoryMap.region[NVMD].element_size = 4096; | 328 | pm8001_ha->memoryMap.region[NVMD].element_size = 4096; |
@@ -264,6 +341,9 @@ static int pm8001_alloc(struct pm8001_hba_info *pm8001_ha) | |||
264 | pm8001_ha->memoryMap.region[CCB_MEM].total_len = PM8001_MAX_CCB * | 341 | pm8001_ha->memoryMap.region[CCB_MEM].total_len = PM8001_MAX_CCB * |
265 | sizeof(struct pm8001_ccb_info); | 342 | sizeof(struct pm8001_ccb_info); |
266 | 343 | ||
344 | /* Memory region for fw flash */ | ||
345 | pm8001_ha->memoryMap.region[FW_FLASH].total_len = 4096; | ||
346 | |||
267 | for (i = 0; i < USI_MAX_MEMCNT; i++) { | 347 | for (i = 0; i < USI_MAX_MEMCNT; i++) { |
268 | if (pm8001_mem_alloc(pm8001_ha->pdev, | 348 | if (pm8001_mem_alloc(pm8001_ha->pdev, |
269 | &pm8001_ha->memoryMap.region[i].virt_ptr, | 349 | &pm8001_ha->memoryMap.region[i].virt_ptr, |
@@ -281,7 +361,7 @@ static int pm8001_alloc(struct pm8001_hba_info *pm8001_ha) | |||
281 | 361 | ||
282 | pm8001_ha->devices = pm8001_ha->memoryMap.region[DEV_MEM].virt_ptr; | 362 | pm8001_ha->devices = pm8001_ha->memoryMap.region[DEV_MEM].virt_ptr; |
283 | for (i = 0; i < PM8001_MAX_DEVICES; i++) { | 363 | for (i = 0; i < PM8001_MAX_DEVICES; i++) { |
284 | pm8001_ha->devices[i].dev_type = NO_DEVICE; | 364 | pm8001_ha->devices[i].dev_type = SAS_PHY_UNUSED; |
285 | pm8001_ha->devices[i].id = i; | 365 | pm8001_ha->devices[i].id = i; |
286 | pm8001_ha->devices[i].device_id = PM8001_MAX_DEVICES; | 366 | pm8001_ha->devices[i].device_id = PM8001_MAX_DEVICES; |
287 | pm8001_ha->devices[i].running_req = 0; | 367 | pm8001_ha->devices[i].running_req = 0; |
@@ -339,10 +419,12 @@ static int pm8001_ioremap(struct pm8001_hba_info *pm8001_ha) | |||
339 | ioremap(pm8001_ha->io_mem[logicalBar].membase, | 419 | ioremap(pm8001_ha->io_mem[logicalBar].membase, |
340 | pm8001_ha->io_mem[logicalBar].memsize); | 420 | pm8001_ha->io_mem[logicalBar].memsize); |
341 | PM8001_INIT_DBG(pm8001_ha, | 421 | PM8001_INIT_DBG(pm8001_ha, |
342 | pm8001_printk("PCI: bar %d, logicalBar %d " | 422 | pm8001_printk("PCI: bar %d, logicalBar %d ", |
343 | "virt_addr=%lx,len=%d\n", bar, logicalBar, | 423 | bar, logicalBar)); |
344 | (unsigned long) | 424 | PM8001_INIT_DBG(pm8001_ha, pm8001_printk( |
345 | pm8001_ha->io_mem[logicalBar].memvirtaddr, | 425 | "base addr %llx virt_addr=%llx len=%d\n", |
426 | (u64)pm8001_ha->io_mem[logicalBar].membase, | ||
427 | (u64)pm8001_ha->io_mem[logicalBar].memvirtaddr, | ||
346 | pm8001_ha->io_mem[logicalBar].memsize)); | 428 | pm8001_ha->io_mem[logicalBar].memsize)); |
347 | } else { | 429 | } else { |
348 | pm8001_ha->io_mem[logicalBar].membase = 0; | 430 | pm8001_ha->io_mem[logicalBar].membase = 0; |
@@ -361,8 +443,9 @@ static int pm8001_ioremap(struct pm8001_hba_info *pm8001_ha) | |||
361 | * @shost: scsi host struct which has been initialized before. | 443 | * @shost: scsi host struct which has been initialized before. |
362 | */ | 444 | */ |
363 | static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev, | 445 | static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev, |
364 | u32 chip_id, | 446 | const struct pci_device_id *ent, |
365 | struct Scsi_Host *shost) | 447 | struct Scsi_Host *shost) |
448 | |||
366 | { | 449 | { |
367 | struct pm8001_hba_info *pm8001_ha; | 450 | struct pm8001_hba_info *pm8001_ha; |
368 | struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); | 451 | struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost); |
@@ -374,7 +457,7 @@ static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev, | |||
374 | 457 | ||
375 | pm8001_ha->pdev = pdev; | 458 | pm8001_ha->pdev = pdev; |
376 | pm8001_ha->dev = &pdev->dev; | 459 | pm8001_ha->dev = &pdev->dev; |
377 | pm8001_ha->chip_id = chip_id; | 460 | pm8001_ha->chip_id = ent->driver_data; |
378 | pm8001_ha->chip = &pm8001_chips[pm8001_ha->chip_id]; | 461 | pm8001_ha->chip = &pm8001_chips[pm8001_ha->chip_id]; |
379 | pm8001_ha->irq = pdev->irq; | 462 | pm8001_ha->irq = pdev->irq; |
380 | pm8001_ha->sas = sha; | 463 | pm8001_ha->sas = sha; |
@@ -382,12 +465,22 @@ static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev, | |||
382 | pm8001_ha->id = pm8001_id++; | 465 | pm8001_ha->id = pm8001_id++; |
383 | pm8001_ha->logging_level = 0x01; | 466 | pm8001_ha->logging_level = 0x01; |
384 | sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); | 467 | sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); |
468 | /* IOMB size is 128 for 8088/89 controllers */ | ||
469 | if (pm8001_ha->chip_id != chip_8001) | ||
470 | pm8001_ha->iomb_size = IOMB_SIZE_SPCV; | ||
471 | else | ||
472 | pm8001_ha->iomb_size = IOMB_SIZE_SPC; | ||
473 | |||
385 | #ifdef PM8001_USE_TASKLET | 474 | #ifdef PM8001_USE_TASKLET |
475 | /** | ||
476 | * default tasklet for non msi-x interrupt handler/first msi-x | ||
477 | * interrupt handler | ||
478 | **/ | ||
386 | tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, | 479 | tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, |
387 | (unsigned long)pm8001_ha); | 480 | (unsigned long)pm8001_ha); |
388 | #endif | 481 | #endif |
389 | pm8001_ioremap(pm8001_ha); | 482 | pm8001_ioremap(pm8001_ha); |
390 | if (!pm8001_alloc(pm8001_ha)) | 483 | if (!pm8001_alloc(pm8001_ha, ent)) |
391 | return pm8001_ha; | 484 | return pm8001_ha; |
392 | pm8001_free(pm8001_ha); | 485 | pm8001_free(pm8001_ha); |
393 | return NULL; | 486 | return NULL; |
@@ -512,21 +605,50 @@ static void pm8001_post_sas_ha_init(struct Scsi_Host *shost, | |||
512 | */ | 605 | */ |
513 | static void pm8001_init_sas_add(struct pm8001_hba_info *pm8001_ha) | 606 | static void pm8001_init_sas_add(struct pm8001_hba_info *pm8001_ha) |
514 | { | 607 | { |
515 | u8 i; | 608 | u8 i, j; |
516 | #ifdef PM8001_READ_VPD | 609 | #ifdef PM8001_READ_VPD |
610 | /* For new SPC controllers WWN is stored in flash vpd | ||
611 | * For SPC/SPCve controllers WWN is stored in EEPROM | ||
612 | * For Older SPC WWN is stored in NVMD | ||
613 | */ | ||
517 | DECLARE_COMPLETION_ONSTACK(completion); | 614 | DECLARE_COMPLETION_ONSTACK(completion); |
518 | struct pm8001_ioctl_payload payload; | 615 | struct pm8001_ioctl_payload payload; |
616 | u16 deviceid; | ||
617 | pci_read_config_word(pm8001_ha->pdev, PCI_DEVICE_ID, &deviceid); | ||
519 | pm8001_ha->nvmd_completion = &completion; | 618 | pm8001_ha->nvmd_completion = &completion; |
520 | payload.minor_function = 0; | 619 | |
521 | payload.length = 128; | 620 | if (pm8001_ha->chip_id == chip_8001) { |
522 | payload.func_specific = kzalloc(128, GFP_KERNEL); | 621 | if (deviceid == 0x8081) { |
622 | payload.minor_function = 4; | ||
623 | payload.length = 4096; | ||
624 | } else { | ||
625 | payload.minor_function = 0; | ||
626 | payload.length = 128; | ||
627 | } | ||
628 | } else { | ||
629 | payload.minor_function = 1; | ||
630 | payload.length = 4096; | ||
631 | } | ||
632 | payload.offset = 0; | ||
633 | payload.func_specific = kzalloc(payload.length, GFP_KERNEL); | ||
523 | PM8001_CHIP_DISP->get_nvmd_req(pm8001_ha, &payload); | 634 | PM8001_CHIP_DISP->get_nvmd_req(pm8001_ha, &payload); |
524 | wait_for_completion(&completion); | 635 | wait_for_completion(&completion); |
636 | |||
637 | for (i = 0, j = 0; i <= 7; i++, j++) { | ||
638 | if (pm8001_ha->chip_id == chip_8001) { | ||
639 | if (deviceid == 0x8081) | ||
640 | pm8001_ha->sas_addr[j] = | ||
641 | payload.func_specific[0x704 + i]; | ||
642 | } else | ||
643 | pm8001_ha->sas_addr[j] = | ||
644 | payload.func_specific[0x804 + i]; | ||
645 | } | ||
646 | |||
525 | for (i = 0; i < pm8001_ha->chip->n_phy; i++) { | 647 | for (i = 0; i < pm8001_ha->chip->n_phy; i++) { |
526 | memcpy(&pm8001_ha->phy[i].dev_sas_addr, pm8001_ha->sas_addr, | 648 | memcpy(&pm8001_ha->phy[i].dev_sas_addr, |
527 | SAS_ADDR_SIZE); | 649 | pm8001_ha->sas_addr, SAS_ADDR_SIZE); |
528 | PM8001_INIT_DBG(pm8001_ha, | 650 | PM8001_INIT_DBG(pm8001_ha, |
529 | pm8001_printk("phy %d sas_addr = %016llx \n", i, | 651 | pm8001_printk("phy %d sas_addr = %016llx\n", i, |
530 | pm8001_ha->phy[i].dev_sas_addr)); | 652 | pm8001_ha->phy[i].dev_sas_addr)); |
531 | } | 653 | } |
532 | #else | 654 | #else |
@@ -547,31 +669,50 @@ static void pm8001_init_sas_add(struct pm8001_hba_info *pm8001_ha) | |||
547 | * @chip_info: our ha struct. | 669 | * @chip_info: our ha struct. |
548 | * @irq_handler: irq_handler | 670 | * @irq_handler: irq_handler |
549 | */ | 671 | */ |
550 | static u32 pm8001_setup_msix(struct pm8001_hba_info *pm8001_ha, | 672 | static u32 pm8001_setup_msix(struct pm8001_hba_info *pm8001_ha) |
551 | irq_handler_t irq_handler) | ||
552 | { | 673 | { |
553 | u32 i = 0, j = 0; | 674 | u32 i = 0, j = 0; |
554 | u32 number_of_intr = 1; | 675 | u32 number_of_intr; |
555 | int flag = 0; | 676 | int flag = 0; |
556 | u32 max_entry; | 677 | u32 max_entry; |
557 | int rc; | 678 | int rc; |
679 | static char intr_drvname[PM8001_MAX_MSIX_VEC][sizeof(DRV_NAME)+3]; | ||
680 | |||
681 | /* SPCv controllers supports 64 msi-x */ | ||
682 | if (pm8001_ha->chip_id == chip_8001) { | ||
683 | number_of_intr = 1; | ||
684 | flag |= IRQF_DISABLED; | ||
685 | } else { | ||
686 | number_of_intr = PM8001_MAX_MSIX_VEC; | ||
687 | flag &= ~IRQF_SHARED; | ||
688 | flag |= IRQF_DISABLED; | ||
689 | } | ||
690 | |||
558 | max_entry = sizeof(pm8001_ha->msix_entries) / | 691 | max_entry = sizeof(pm8001_ha->msix_entries) / |
559 | sizeof(pm8001_ha->msix_entries[0]); | 692 | sizeof(pm8001_ha->msix_entries[0]); |
560 | flag |= IRQF_DISABLED; | ||
561 | for (i = 0; i < max_entry ; i++) | 693 | for (i = 0; i < max_entry ; i++) |
562 | pm8001_ha->msix_entries[i].entry = i; | 694 | pm8001_ha->msix_entries[i].entry = i; |
563 | rc = pci_enable_msix(pm8001_ha->pdev, pm8001_ha->msix_entries, | 695 | rc = pci_enable_msix(pm8001_ha->pdev, pm8001_ha->msix_entries, |
564 | number_of_intr); | 696 | number_of_intr); |
565 | pm8001_ha->number_of_intr = number_of_intr; | 697 | pm8001_ha->number_of_intr = number_of_intr; |
566 | if (!rc) { | 698 | if (!rc) { |
699 | PM8001_INIT_DBG(pm8001_ha, pm8001_printk( | ||
700 | "pci_enable_msix request ret:%d no of intr %d\n", | ||
701 | rc, pm8001_ha->number_of_intr)); | ||
702 | |||
703 | for (i = 0; i < number_of_intr; i++) | ||
704 | pm8001_ha->outq[i] = i; | ||
705 | |||
567 | for (i = 0; i < number_of_intr; i++) { | 706 | for (i = 0; i < number_of_intr; i++) { |
707 | snprintf(intr_drvname[i], sizeof(intr_drvname[0]), | ||
708 | DRV_NAME"%d", i); | ||
568 | if (request_irq(pm8001_ha->msix_entries[i].vector, | 709 | if (request_irq(pm8001_ha->msix_entries[i].vector, |
569 | irq_handler, flag, DRV_NAME, | 710 | pm8001_interrupt_handler_msix, flag, |
570 | SHOST_TO_SAS_HA(pm8001_ha->shost))) { | 711 | intr_drvname[i], &pm8001_ha->outq[i])) { |
571 | for (j = 0; j < i; j++) | 712 | for (j = 0; j < i; j++) |
572 | free_irq( | 713 | free_irq( |
573 | pm8001_ha->msix_entries[j].vector, | 714 | pm8001_ha->msix_entries[j].vector, |
574 | SHOST_TO_SAS_HA(pm8001_ha->shost)); | 715 | &pm8001_ha->outq[j]); |
575 | pci_disable_msix(pm8001_ha->pdev); | 716 | pci_disable_msix(pm8001_ha->pdev); |
576 | break; | 717 | break; |
577 | } | 718 | } |
@@ -588,22 +729,24 @@ static u32 pm8001_setup_msix(struct pm8001_hba_info *pm8001_ha, | |||
588 | static u32 pm8001_request_irq(struct pm8001_hba_info *pm8001_ha) | 729 | static u32 pm8001_request_irq(struct pm8001_hba_info *pm8001_ha) |
589 | { | 730 | { |
590 | struct pci_dev *pdev; | 731 | struct pci_dev *pdev; |
591 | irq_handler_t irq_handler = pm8001_interrupt; | ||
592 | int rc; | 732 | int rc; |
593 | 733 | ||
594 | pdev = pm8001_ha->pdev; | 734 | pdev = pm8001_ha->pdev; |
595 | 735 | ||
596 | #ifdef PM8001_USE_MSIX | 736 | #ifdef PM8001_USE_MSIX |
597 | if (pci_find_capability(pdev, PCI_CAP_ID_MSIX)) | 737 | if (pci_find_capability(pdev, PCI_CAP_ID_MSIX)) |
598 | return pm8001_setup_msix(pm8001_ha, irq_handler); | 738 | return pm8001_setup_msix(pm8001_ha); |
599 | else | 739 | else { |
740 | PM8001_INIT_DBG(pm8001_ha, | ||
741 | pm8001_printk("MSIX not supported!!!\n")); | ||
600 | goto intx; | 742 | goto intx; |
743 | } | ||
601 | #endif | 744 | #endif |
602 | 745 | ||
603 | intx: | 746 | intx: |
604 | /* initialize the INT-X interrupt */ | 747 | /* initialize the INT-X interrupt */ |
605 | rc = request_irq(pdev->irq, irq_handler, IRQF_SHARED, DRV_NAME, | 748 | rc = request_irq(pdev->irq, pm8001_interrupt_handler_intx, IRQF_SHARED, |
606 | SHOST_TO_SAS_HA(pm8001_ha->shost)); | 749 | DRV_NAME, SHOST_TO_SAS_HA(pm8001_ha->shost)); |
607 | return rc; | 750 | return rc; |
608 | } | 751 | } |
609 | 752 | ||
@@ -621,12 +764,13 @@ static int pm8001_pci_probe(struct pci_dev *pdev, | |||
621 | { | 764 | { |
622 | unsigned int rc; | 765 | unsigned int rc; |
623 | u32 pci_reg; | 766 | u32 pci_reg; |
767 | u8 i = 0; | ||
624 | struct pm8001_hba_info *pm8001_ha; | 768 | struct pm8001_hba_info *pm8001_ha; |
625 | struct Scsi_Host *shost = NULL; | 769 | struct Scsi_Host *shost = NULL; |
626 | const struct pm8001_chip_info *chip; | 770 | const struct pm8001_chip_info *chip; |
627 | 771 | ||
628 | dev_printk(KERN_INFO, &pdev->dev, | 772 | dev_printk(KERN_INFO, &pdev->dev, |
629 | "pm8001: driver version %s\n", DRV_VERSION); | 773 | "pm80xx: driver version %s\n", DRV_VERSION); |
630 | rc = pci_enable_device(pdev); | 774 | rc = pci_enable_device(pdev); |
631 | if (rc) | 775 | if (rc) |
632 | goto err_out_enable; | 776 | goto err_out_enable; |
@@ -665,25 +809,39 @@ static int pm8001_pci_probe(struct pci_dev *pdev, | |||
665 | goto err_out_free; | 809 | goto err_out_free; |
666 | } | 810 | } |
667 | pci_set_drvdata(pdev, SHOST_TO_SAS_HA(shost)); | 811 | pci_set_drvdata(pdev, SHOST_TO_SAS_HA(shost)); |
668 | pm8001_ha = pm8001_pci_alloc(pdev, chip_8001, shost); | 812 | /* ent->driver variable is used to differentiate between controllers */ |
813 | pm8001_ha = pm8001_pci_alloc(pdev, ent, shost); | ||
669 | if (!pm8001_ha) { | 814 | if (!pm8001_ha) { |
670 | rc = -ENOMEM; | 815 | rc = -ENOMEM; |
671 | goto err_out_free; | 816 | goto err_out_free; |
672 | } | 817 | } |
673 | list_add_tail(&pm8001_ha->list, &hba_list); | 818 | list_add_tail(&pm8001_ha->list, &hba_list); |
674 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha, 0x252acbcd); | 819 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha); |
675 | rc = PM8001_CHIP_DISP->chip_init(pm8001_ha); | 820 | rc = PM8001_CHIP_DISP->chip_init(pm8001_ha); |
676 | if (rc) | 821 | if (rc) { |
822 | PM8001_FAIL_DBG(pm8001_ha, pm8001_printk( | ||
823 | "chip_init failed [ret: %d]\n", rc)); | ||
677 | goto err_out_ha_free; | 824 | goto err_out_ha_free; |
825 | } | ||
678 | 826 | ||
679 | rc = scsi_add_host(shost, &pdev->dev); | 827 | rc = scsi_add_host(shost, &pdev->dev); |
680 | if (rc) | 828 | if (rc) |
681 | goto err_out_ha_free; | 829 | goto err_out_ha_free; |
682 | rc = pm8001_request_irq(pm8001_ha); | 830 | rc = pm8001_request_irq(pm8001_ha); |
683 | if (rc) | 831 | if (rc) { |
832 | PM8001_FAIL_DBG(pm8001_ha, pm8001_printk( | ||
833 | "pm8001_request_irq failed [ret: %d]\n", rc)); | ||
684 | goto err_out_shost; | 834 | goto err_out_shost; |
835 | } | ||
836 | |||
837 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha, 0); | ||
838 | if (pm8001_ha->chip_id != chip_8001) { | ||
839 | for (i = 1; i < pm8001_ha->number_of_intr; i++) | ||
840 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha, i); | ||
841 | /* setup thermal configuration. */ | ||
842 | pm80xx_set_thermal_config(pm8001_ha); | ||
843 | } | ||
685 | 844 | ||
686 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha); | ||
687 | pm8001_init_sas_add(pm8001_ha); | 845 | pm8001_init_sas_add(pm8001_ha); |
688 | pm8001_post_sas_ha_init(shost, chip); | 846 | pm8001_post_sas_ha_init(shost, chip); |
689 | rc = sas_register_ha(SHOST_TO_SAS_HA(shost)); | 847 | rc = sas_register_ha(SHOST_TO_SAS_HA(shost)); |
@@ -719,14 +877,15 @@ static void pm8001_pci_remove(struct pci_dev *pdev) | |||
719 | sas_remove_host(pm8001_ha->shost); | 877 | sas_remove_host(pm8001_ha->shost); |
720 | list_del(&pm8001_ha->list); | 878 | list_del(&pm8001_ha->list); |
721 | scsi_remove_host(pm8001_ha->shost); | 879 | scsi_remove_host(pm8001_ha->shost); |
722 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha); | 880 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha, 0xFF); |
723 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha, 0x252acbcd); | 881 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha); |
724 | 882 | ||
725 | #ifdef PM8001_USE_MSIX | 883 | #ifdef PM8001_USE_MSIX |
726 | for (i = 0; i < pm8001_ha->number_of_intr; i++) | 884 | for (i = 0; i < pm8001_ha->number_of_intr; i++) |
727 | synchronize_irq(pm8001_ha->msix_entries[i].vector); | 885 | synchronize_irq(pm8001_ha->msix_entries[i].vector); |
728 | for (i = 0; i < pm8001_ha->number_of_intr; i++) | 886 | for (i = 0; i < pm8001_ha->number_of_intr; i++) |
729 | free_irq(pm8001_ha->msix_entries[i].vector, sha); | 887 | free_irq(pm8001_ha->msix_entries[i].vector, |
888 | &pm8001_ha->outq[i]); | ||
730 | pci_disable_msix(pdev); | 889 | pci_disable_msix(pdev); |
731 | #else | 890 | #else |
732 | free_irq(pm8001_ha->irq, sha); | 891 | free_irq(pm8001_ha->irq, sha); |
@@ -763,13 +922,14 @@ static int pm8001_pci_suspend(struct pci_dev *pdev, pm_message_t state) | |||
763 | printk(KERN_ERR " PCI PM not supported\n"); | 922 | printk(KERN_ERR " PCI PM not supported\n"); |
764 | return -ENODEV; | 923 | return -ENODEV; |
765 | } | 924 | } |
766 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha); | 925 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha, 0xFF); |
767 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha, 0x252acbcd); | 926 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha); |
768 | #ifdef PM8001_USE_MSIX | 927 | #ifdef PM8001_USE_MSIX |
769 | for (i = 0; i < pm8001_ha->number_of_intr; i++) | 928 | for (i = 0; i < pm8001_ha->number_of_intr; i++) |
770 | synchronize_irq(pm8001_ha->msix_entries[i].vector); | 929 | synchronize_irq(pm8001_ha->msix_entries[i].vector); |
771 | for (i = 0; i < pm8001_ha->number_of_intr; i++) | 930 | for (i = 0; i < pm8001_ha->number_of_intr; i++) |
772 | free_irq(pm8001_ha->msix_entries[i].vector, sha); | 931 | free_irq(pm8001_ha->msix_entries[i].vector, |
932 | &pm8001_ha->outq[i]); | ||
773 | pci_disable_msix(pdev); | 933 | pci_disable_msix(pdev); |
774 | #else | 934 | #else |
775 | free_irq(pm8001_ha->irq, sha); | 935 | free_irq(pm8001_ha->irq, sha); |
@@ -798,6 +958,7 @@ static int pm8001_pci_resume(struct pci_dev *pdev) | |||
798 | struct sas_ha_struct *sha = pci_get_drvdata(pdev); | 958 | struct sas_ha_struct *sha = pci_get_drvdata(pdev); |
799 | struct pm8001_hba_info *pm8001_ha; | 959 | struct pm8001_hba_info *pm8001_ha; |
800 | int rc; | 960 | int rc; |
961 | u8 i = 0; | ||
801 | u32 device_state; | 962 | u32 device_state; |
802 | pm8001_ha = sha->lldd_ha; | 963 | pm8001_ha = sha->lldd_ha; |
803 | device_state = pdev->current_state; | 964 | device_state = pdev->current_state; |
@@ -820,19 +981,33 @@ static int pm8001_pci_resume(struct pci_dev *pdev) | |||
820 | if (rc) | 981 | if (rc) |
821 | goto err_out_disable; | 982 | goto err_out_disable; |
822 | 983 | ||
823 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha, 0x252acbcd); | 984 | /* chip soft rst only for spc */ |
985 | if (pm8001_ha->chip_id == chip_8001) { | ||
986 | PM8001_CHIP_DISP->chip_soft_rst(pm8001_ha); | ||
987 | PM8001_INIT_DBG(pm8001_ha, | ||
988 | pm8001_printk("chip soft reset successful\n")); | ||
989 | } | ||
824 | rc = PM8001_CHIP_DISP->chip_init(pm8001_ha); | 990 | rc = PM8001_CHIP_DISP->chip_init(pm8001_ha); |
825 | if (rc) | 991 | if (rc) |
826 | goto err_out_disable; | 992 | goto err_out_disable; |
827 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha); | 993 | |
994 | /* disable all the interrupt bits */ | ||
995 | PM8001_CHIP_DISP->interrupt_disable(pm8001_ha, 0xFF); | ||
996 | |||
828 | rc = pm8001_request_irq(pm8001_ha); | 997 | rc = pm8001_request_irq(pm8001_ha); |
829 | if (rc) | 998 | if (rc) |
830 | goto err_out_disable; | 999 | goto err_out_disable; |
831 | #ifdef PM8001_USE_TASKLET | 1000 | #ifdef PM8001_USE_TASKLET |
1001 | /* default tasklet for non msi-x interrupt handler/first msi-x | ||
1002 | * interrupt handler */ | ||
832 | tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, | 1003 | tasklet_init(&pm8001_ha->tasklet, pm8001_tasklet, |
833 | (unsigned long)pm8001_ha); | 1004 | (unsigned long)pm8001_ha); |
834 | #endif | 1005 | #endif |
835 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha); | 1006 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha, 0); |
1007 | if (pm8001_ha->chip_id != chip_8001) { | ||
1008 | for (i = 1; i < pm8001_ha->number_of_intr; i++) | ||
1009 | PM8001_CHIP_DISP->interrupt_enable(pm8001_ha, i); | ||
1010 | } | ||
836 | scsi_unblock_requests(pm8001_ha->shost); | 1011 | scsi_unblock_requests(pm8001_ha->shost); |
837 | return 0; | 1012 | return 0; |
838 | 1013 | ||
@@ -843,14 +1018,45 @@ err_out_enable: | |||
843 | return rc; | 1018 | return rc; |
844 | } | 1019 | } |
845 | 1020 | ||
1021 | /* update of pci device, vendor id and driver data with | ||
1022 | * unique value for each of the controller | ||
1023 | */ | ||
846 | static struct pci_device_id pm8001_pci_table[] = { | 1024 | static struct pci_device_id pm8001_pci_table[] = { |
847 | { | 1025 | { PCI_VDEVICE(PMC_Sierra, 0x8001), chip_8001 }, |
848 | PCI_VDEVICE(PMC_Sierra, 0x8001), chip_8001 | ||
849 | }, | ||
850 | { | 1026 | { |
851 | PCI_DEVICE(0x117c, 0x0042), | 1027 | PCI_DEVICE(0x117c, 0x0042), |
852 | .driver_data = chip_8001 | 1028 | .driver_data = chip_8001 |
853 | }, | 1029 | }, |
1030 | /* Support for SPC/SPCv/SPCve controllers */ | ||
1031 | { PCI_VDEVICE(ADAPTEC2, 0x8001), chip_8001 }, | ||
1032 | { PCI_VDEVICE(PMC_Sierra, 0x8008), chip_8008 }, | ||
1033 | { PCI_VDEVICE(ADAPTEC2, 0x8008), chip_8008 }, | ||
1034 | { PCI_VDEVICE(PMC_Sierra, 0x8018), chip_8018 }, | ||
1035 | { PCI_VDEVICE(ADAPTEC2, 0x8018), chip_8018 }, | ||
1036 | { PCI_VDEVICE(PMC_Sierra, 0x8009), chip_8009 }, | ||
1037 | { PCI_VDEVICE(ADAPTEC2, 0x8009), chip_8009 }, | ||
1038 | { PCI_VDEVICE(PMC_Sierra, 0x8019), chip_8019 }, | ||
1039 | { PCI_VDEVICE(ADAPTEC2, 0x8019), chip_8019 }, | ||
1040 | { PCI_VENDOR_ID_ADAPTEC2, 0x8081, | ||
1041 | PCI_VENDOR_ID_ADAPTEC2, 0x0400, 0, 0, chip_8001 }, | ||
1042 | { PCI_VENDOR_ID_ADAPTEC2, 0x8081, | ||
1043 | PCI_VENDOR_ID_ADAPTEC2, 0x0800, 0, 0, chip_8001 }, | ||
1044 | { PCI_VENDOR_ID_ADAPTEC2, 0x8088, | ||
1045 | PCI_VENDOR_ID_ADAPTEC2, 0x0008, 0, 0, chip_8008 }, | ||
1046 | { PCI_VENDOR_ID_ADAPTEC2, 0x8088, | ||
1047 | PCI_VENDOR_ID_ADAPTEC2, 0x0800, 0, 0, chip_8008 }, | ||
1048 | { PCI_VENDOR_ID_ADAPTEC2, 0x8089, | ||
1049 | PCI_VENDOR_ID_ADAPTEC2, 0x0008, 0, 0, chip_8009 }, | ||
1050 | { PCI_VENDOR_ID_ADAPTEC2, 0x8089, | ||
1051 | PCI_VENDOR_ID_ADAPTEC2, 0x0800, 0, 0, chip_8009 }, | ||
1052 | { PCI_VENDOR_ID_ADAPTEC2, 0x8088, | ||
1053 | PCI_VENDOR_ID_ADAPTEC2, 0x0016, 0, 0, chip_8018 }, | ||
1054 | { PCI_VENDOR_ID_ADAPTEC2, 0x8088, | ||
1055 | PCI_VENDOR_ID_ADAPTEC2, 0x1600, 0, 0, chip_8018 }, | ||
1056 | { PCI_VENDOR_ID_ADAPTEC2, 0x8089, | ||
1057 | PCI_VENDOR_ID_ADAPTEC2, 0x0016, 0, 0, chip_8019 }, | ||
1058 | { PCI_VENDOR_ID_ADAPTEC2, 0x8089, | ||
1059 | PCI_VENDOR_ID_ADAPTEC2, 0x1600, 0, 0, chip_8019 }, | ||
854 | {} /* terminate list */ | 1060 | {} /* terminate list */ |
855 | }; | 1061 | }; |
856 | 1062 | ||
@@ -870,7 +1076,7 @@ static int __init pm8001_init(void) | |||
870 | { | 1076 | { |
871 | int rc = -ENOMEM; | 1077 | int rc = -ENOMEM; |
872 | 1078 | ||
873 | pm8001_wq = alloc_workqueue("pm8001", 0, 0); | 1079 | pm8001_wq = alloc_workqueue("pm80xx", 0, 0); |
874 | if (!pm8001_wq) | 1080 | if (!pm8001_wq) |
875 | goto err; | 1081 | goto err; |
876 | 1082 | ||
@@ -902,7 +1108,8 @@ module_init(pm8001_init); | |||
902 | module_exit(pm8001_exit); | 1108 | module_exit(pm8001_exit); |
903 | 1109 | ||
904 | MODULE_AUTHOR("Jack Wang <jack_wang@usish.com>"); | 1110 | MODULE_AUTHOR("Jack Wang <jack_wang@usish.com>"); |
905 | MODULE_DESCRIPTION("PMC-Sierra PM8001 SAS/SATA controller driver"); | 1111 | MODULE_DESCRIPTION( |
1112 | "PMC-Sierra PM8001/8081/8088/8089 SAS/SATA controller driver"); | ||
906 | MODULE_VERSION(DRV_VERSION); | 1113 | MODULE_VERSION(DRV_VERSION); |
907 | MODULE_LICENSE("GPL"); | 1114 | MODULE_LICENSE("GPL"); |
908 | MODULE_DEVICE_TABLE(pci, pm8001_pci_table); | 1115 | MODULE_DEVICE_TABLE(pci, pm8001_pci_table); |