1/* 2 * linux/drivers/acorn/scsi/cumana_2.c 3 * 4 * Copyright (C) 1997-2000 Russell King 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 * 10 * Changelog: 11 * 30-08-1997 RMK 0.0.0 Created, READONLY version. 12 * 22-01-1998 RMK 0.0.1 Updated to 2.1.80. 13 * 15-04-1998 RMK 0.0.1 Only do PIO if FAS216 will allow it. 14 * 02-05-1998 RMK 0.0.2 Updated & added DMA support. 15 * 27-06-1998 RMK Changed asm/delay.h to linux/delay.h 16 * 18-08-1998 RMK 0.0.3 Fixed synchronous transfer depth. 17 * 02-04-2000 RMK 0.0.4 Updated for new error handling code. 18 */ 19#include <linux/module.h> 20#include <linux/blk.h> 21#include <linux/kernel.h> 22#include <linux/string.h> 23#include <linux/ioport.h> 24#include <linux/sched.h> 25#include <linux/proc_fs.h> 26#include <linux/unistd.h> 27#include <linux/stat.h> 28#include <linux/delay.h> 29#include <linux/pci.h> 30#include <linux/init.h> 31 32#include <asm/dma.h> 33#include <asm/ecard.h> 34#include <asm/io.h> 35#include <asm/irq.h> 36#include <asm/pgtable.h> 37 38#include "../../scsi/sd.h" 39#include "../../scsi/hosts.h" 40#include "fas216.h" 41 42#include <scsi/scsicam.h> 43 44/* Configuration */ 45#define CUMANASCSI2_XTALFREQ 40 46#define CUMANASCSI2_ASYNC_PERIOD 200 47#define CUMANASCSI2_SYNC_DEPTH 7 48 49/* 50 * List of devices that the driver will recognise 51 */ 52#define CUMANASCSI2_LIST { MANU_CUMANA, PROD_CUMANA_SCSI_2 } 53 54#define CUMANASCSI2_STATUS (0) 55#define STATUS_INT (1 << 0) 56#define STATUS_DRQ (1 << 1) 57#define STATUS_LATCHED (1 << 3) 58 59#define CUMANASCSI2_ALATCH (5) 60#define ALATCH_ENA_INT (3) 61#define ALATCH_DIS_INT (2) 62#define ALATCH_ENA_TERM (5) 63#define ALATCH_DIS_TERM (4) 64#define ALATCH_ENA_BIT32 (11) 65#define ALATCH_DIS_BIT32 (10) 66#define ALATCH_ENA_DMA (13) 67#define ALATCH_DIS_DMA (12) 68#define ALATCH_DMA_OUT (15) 69#define ALATCH_DMA_IN (14) 70 71#define CUMANASCSI2_PSEUDODMA (0x80) 72 73#define CUMANASCSI2_FAS216_OFFSET (0xc0) 74#define CUMANASCSI2_FAS216_SHIFT 0 75 76/* 77 * Version 78 */ 79#define VER_MAJOR 0 80#define VER_MINOR 0 81#define VER_PATCH 4 82 83static struct expansion_card *ecs[MAX_ECARDS]; 84 85/* 86 * Use term=0,1,0,0,0 to turn terminators on/off 87 */ 88static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 }; 89 90#define NR_SG 256 91 92typedef struct { 93 FAS216_Info info; 94 95 /* other info... */ 96 unsigned int status; /* card status register */ 97 unsigned int alatch; /* Control register */ 98 unsigned int terms; /* Terminator state */ 99 unsigned int dmaarea; /* Pseudo DMA area */ 100 struct scatterlist sg[NR_SG]; /* Scatter DMA list */ 101} CumanaScsi2_Info; 102 103#define CSTATUS_IRQ (1 << 0) 104#define CSTATUS_DRQ (1 << 1) 105 106/* Prototype: void cumanascsi_2_irqenable(ec, irqnr) 107 * Purpose : Enable interrupts on Cumana SCSI 2 card 108 * Params : ec - expansion card structure 109 * : irqnr - interrupt number 110 */ 111static void 112cumanascsi_2_irqenable(struct expansion_card *ec, int irqnr) 113{ 114 unsigned int port = (unsigned int)ec->irq_data; 115 outb(ALATCH_ENA_INT, port); 116} 117 118/* Prototype: void cumanascsi_2_irqdisable(ec, irqnr) 119 * Purpose : Disable interrupts on Cumana SCSI 2 card 120 * Params : ec - expansion card structure 121 * : irqnr - interrupt number 122 */ 123static void 124cumanascsi_2_irqdisable(struct expansion_card *ec, int irqnr) 125{ 126 unsigned int port = (unsigned int)ec->irq_data; 127 outb(ALATCH_DIS_INT, port); 128} 129 130static const expansioncard_ops_t cumanascsi_2_ops = { 131 cumanascsi_2_irqenable, 132 cumanascsi_2_irqdisable, 133 NULL, 134 NULL, 135 NULL, 136 NULL 137}; 138 139/* Prototype: void cumanascsi_2_terminator_ctl(host, on_off) 140 * Purpose : Turn the Cumana SCSI 2 terminators on or off 141 * Params : host - card to turn on/off 142 * : on_off - !0 to turn on, 0 to turn off 143 */ 144static void 145cumanascsi_2_terminator_ctl(struct Scsi_Host *host, int on_off) 146{ 147 CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; 148 149 if (on_off) { 150 info->terms = 1; 151 outb (ALATCH_ENA_TERM, info->alatch); 152 } else { 153 info->terms = 0; 154 outb (ALATCH_DIS_TERM, info->alatch); 155 } 156} 157 158/* Prototype: void cumanascsi_2_intr(irq, *dev_id, *regs) 159 * Purpose : handle interrupts from Cumana SCSI 2 card 160 * Params : irq - interrupt number 161 * dev_id - user-defined (Scsi_Host structure) 162 * regs - processor registers at interrupt 163 */ 164static void 165cumanascsi_2_intr(int irq, void *dev_id, struct pt_regs *regs) 166{ 167 struct Scsi_Host *host = (struct Scsi_Host *)dev_id; 168 169 fas216_intr(host); 170} 171 172/* Prototype: fasdmatype_t cumanascsi_2_dma_setup(host, SCpnt, direction, min_type) 173 * Purpose : initialises DMA/PIO 174 * Params : host - host 175 * SCpnt - command 176 * direction - DMA on to/off of card 177 * min_type - minimum DMA support that we must have for this transfer 178 * Returns : type of transfer to be performed 179 */ 180static fasdmatype_t 181cumanascsi_2_dma_setup(struct Scsi_Host *host, Scsi_Pointer *SCp, 182 fasdmadir_t direction, fasdmatype_t min_type) 183{ 184 CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; 185 int dmach = host->dma_channel; 186 187 outb(ALATCH_DIS_DMA, info->alatch); 188 189 if (dmach != NO_DMA && 190 (min_type == fasdma_real_all || SCp->this_residual >= 512)) { 191 int bufs = SCp->buffers_residual; 192 int pci_dir, dma_dir, alatch_dir; 193 194 if (bufs) 195 memcpy(info->sg + 1, SCp->buffer + 1, 196 sizeof(struct scatterlist) * bufs); 197 info->sg[0].address = SCp->ptr; 198 info->sg[0].page = NULL; 199 info->sg[0].length = SCp->this_residual; 200 201 if (direction == DMA_OUT) 202 pci_dir = PCI_DMA_TODEVICE, 203 dma_dir = DMA_MODE_WRITE, 204 alatch_dir = ALATCH_DMA_OUT; 205 else 206 pci_dir = PCI_DMA_FROMDEVICE, 207 dma_dir = DMA_MODE_READ, 208 alatch_dir = ALATCH_DMA_IN; 209 210 pci_map_sg(NULL, info->sg, bufs + 1, pci_dir); 211 212 disable_dma(dmach); 213 set_dma_sg(dmach, info->sg, bufs + 1); 214 outb(alatch_dir, info->alatch); 215 set_dma_mode(dmach, dma_dir); 216 enable_dma(dmach); 217 outb(ALATCH_ENA_DMA, info->alatch); 218 outb(ALATCH_DIS_BIT32, info->alatch); 219 return fasdma_real_all; 220 } 221 222 /* 223 * If we're not doing DMA, 224 * we'll do pseudo DMA 225 */ 226 return fasdma_pio; 227} 228 229/* 230 * Prototype: void cumanascsi_2_dma_pseudo(host, SCpnt, direction, transfer) 231 * Purpose : handles pseudo DMA 232 * Params : host - host 233 * SCpnt - command 234 * direction - DMA on to/off of card 235 * transfer - minimum number of bytes we expect to transfer 236 */ 237static void 238cumanascsi_2_dma_pseudo(struct Scsi_Host *host, Scsi_Pointer *SCp, 239 fasdmadir_t direction, int transfer) 240{ 241 CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; 242 unsigned int length; 243 unsigned char *addr; 244 245 length = SCp->this_residual; 246 addr = SCp->ptr; 247 248 if (direction == DMA_OUT) 249 printk ("PSEUDO_OUT???\n"); 250 else { 251 if (transfer && (transfer & 255)) { 252 while (length >= 256) { 253 unsigned int status = inb(info->status); 254 255 if (status & STATUS_INT) 256 goto end; 257 258 if (!(status & STATUS_DRQ)) 259 continue; 260 261 insw(info->dmaarea, addr, 256 >> 1); 262 addr += 256; 263 length -= 256; 264 } 265 } 266 267 while (length > 0) { 268 unsigned long word; 269 unsigned int status = inb(info->status); 270 271 if (status & STATUS_INT) 272 goto end; 273 274 if (!(status & STATUS_DRQ)) 275 continue; 276 277 word = inw (info->dmaarea); 278 *addr++ = word; 279 if (--length > 0) { 280 *addr++ = word >> 8; 281 length --; 282 } 283 } 284 } 285 286end: 287} 288 289/* Prototype: int cumanascsi_2_dma_stop(host, SCpnt) 290 * Purpose : stops DMA/PIO 291 * Params : host - host 292 * SCpnt - command 293 */ 294static void 295cumanascsi_2_dma_stop(struct Scsi_Host *host, Scsi_Pointer *SCp) 296{ 297 CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; 298 if (host->dma_channel != NO_DMA) { 299 outb(ALATCH_DIS_DMA, info->alatch); 300 disable_dma(host->dma_channel); 301 } 302} 303 304/* Prototype: int cumanascsi_2_detect(Scsi_Host_Template * tpnt) 305 * Purpose : initialises Cumana SCSI 2 driver 306 * Params : tpnt - template for this SCSI adapter 307 * Returns : >0 if host found, 0 otherwise. 308 */ 309int 310cumanascsi_2_detect(Scsi_Host_Template *tpnt) 311{ 312 static const card_ids cumanascsi_2_cids[] = 313 { CUMANASCSI2_LIST, { 0xffff, 0xffff} }; 314 int count = 0; 315 struct Scsi_Host *host; 316 317 tpnt->proc_name = "cumanascs2"; 318 memset(ecs, 0, sizeof (ecs)); 319 320 ecard_startfind(); 321 322 while (1) { 323 CumanaScsi2_Info *info; 324 325 ecs[count] = ecard_find(0, cumanascsi_2_cids); 326 if (!ecs[count]) 327 break; 328 329 ecard_claim(ecs[count]); 330 331 host = scsi_register(tpnt, sizeof (CumanaScsi2_Info)); 332 if (!host) { 333 ecard_release(ecs[count]); 334 break; 335 } 336 337 host->io_port = ecard_address(ecs[count], ECARD_MEMC, 0); 338 host->irq = ecs[count]->irq; 339 host->dma_channel = ecs[count]->dma; 340 info = (CumanaScsi2_Info *)host->hostdata; 341 342 info->terms = term[count] ? 1 : 0; 343 cumanascsi_2_terminator_ctl(host, info->terms); 344 345 info->info.scsi.io_port = host->io_port + CUMANASCSI2_FAS216_OFFSET; 346 info->info.scsi.io_shift = CUMANASCSI2_FAS216_SHIFT; 347 info->info.scsi.irq = host->irq; 348 info->info.ifcfg.clockrate = CUMANASCSI2_XTALFREQ; 349 info->info.ifcfg.select_timeout = 255; 350 info->info.ifcfg.asyncperiod = CUMANASCSI2_ASYNC_PERIOD; 351 info->info.ifcfg.sync_max_depth = CUMANASCSI2_SYNC_DEPTH; 352 info->info.ifcfg.cntl3 = CNTL3_BS8 | CNTL3_FASTSCSI | CNTL3_FASTCLK; 353 info->info.ifcfg.disconnect_ok = 1; 354 info->info.ifcfg.wide_max_size = 0; 355 info->info.dma.setup = cumanascsi_2_dma_setup; 356 info->info.dma.pseudo = cumanascsi_2_dma_pseudo; 357 info->info.dma.stop = cumanascsi_2_dma_stop; 358 info->dmaarea = host->io_port + CUMANASCSI2_PSEUDODMA; 359 info->status = host->io_port + CUMANASCSI2_STATUS; 360 info->alatch = host->io_port + CUMANASCSI2_ALATCH; 361 362 ecs[count]->irqaddr = (unsigned char *)ioaddr(info->status); 363 ecs[count]->irqmask = STATUS_INT; 364 ecs[count]->irq_data = (void *)info->alatch; 365 ecs[count]->ops = (expansioncard_ops_t *)&cumanascsi_2_ops; 366 367 request_region(host->io_port + CUMANASCSI2_FAS216_OFFSET, 368 16 << CUMANASCSI2_FAS216_SHIFT, "cumanascsi2-fas"); 369 370 if (host->irq != NO_IRQ && 371 request_irq(host->irq, cumanascsi_2_intr, 372 SA_INTERRUPT, "cumanascsi2", host)) { 373 printk("scsi%d: IRQ%d not free, interrupts disabled\n", 374 host->host_no, host->irq); 375 host->irq = NO_IRQ; 376 info->info.scsi.irq = NO_IRQ; 377 } 378 379 if (host->dma_channel != NO_DMA && 380 request_dma(host->dma_channel, "cumanascsi2")) { 381 printk("scsi%d: DMA%d not free, DMA disabled\n", 382 host->host_no, host->dma_channel); 383 host->dma_channel = NO_DMA; 384 } 385 386 fas216_init(host); 387 ++count; 388 } 389 return count; 390} 391 392/* Prototype: int cumanascsi_2_release(struct Scsi_Host * host) 393 * Purpose : releases all resources used by this adapter 394 * Params : host - driver host structure to return info for. 395 */ 396int cumanascsi_2_release(struct Scsi_Host *host) 397{ 398 int i; 399 400 fas216_release(host); 401 402 if (host->irq != NO_IRQ) 403 free_irq(host->irq, host); 404 if (host->dma_channel != NO_DMA) 405 free_dma(host->dma_channel); 406 release_region(host->io_port + CUMANASCSI2_FAS216_OFFSET, 407 16 << CUMANASCSI2_FAS216_SHIFT); 408 409 for (i = 0; i < MAX_ECARDS; i++) 410 if (ecs[i] && host->io_port == ecard_address (ecs[i], ECARD_MEMC, 0)) 411 ecard_release (ecs[i]); 412 return 0; 413} 414 415/* Prototype: const char *cumanascsi_2_info(struct Scsi_Host * host) 416 * Purpose : returns a descriptive string about this interface, 417 * Params : host - driver host structure to return info for. 418 * Returns : pointer to a static buffer containing null terminated string. 419 */ 420const char *cumanascsi_2_info(struct Scsi_Host *host) 421{ 422 CumanaScsi2_Info *info = (CumanaScsi2_Info *)host->hostdata; 423 static char string[100], *p; 424 425 p = string; 426 p += sprintf(p, "%s ", host->hostt->name); 427 p += fas216_info(&info->info, p); 428 p += sprintf(p, "v%d.%d.%d terminators o%s", 429 VER_MAJOR, VER_MINOR, VER_PATCH, 430 info->terms ? "n" : "ff"); 431 432 return string; 433} 434 435/* Prototype: int cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length) 436 * Purpose : Set a driver specific function 437 * Params : host - host to setup 438 * : buffer - buffer containing string describing operation 439 * : length - length of string 440 * Returns : -EINVAL, or 0 441 */ 442static int 443cumanascsi_2_set_proc_info(struct Scsi_Host *host, char *buffer, int length) 444{ 445 int ret = length; 446 447 if (length >= 11 && strcmp(buffer, "CUMANASCSI2") == 0) { 448 buffer += 11; 449 length -= 11; 450 451 if (length >= 5 && strncmp(buffer, "term=", 5) == 0) { 452 if (buffer[5] == '1') 453 cumanascsi_2_terminator_ctl(host, 1); 454 else if (buffer[5] == '0') 455 cumanascsi_2_terminator_ctl(host, 0); 456 else 457 ret = -EINVAL; 458 } else 459 ret = -EINVAL; 460 } else 461 ret = -EINVAL; 462 463 return ret; 464} 465 466/* Prototype: int cumanascsi_2_proc_info(char *buffer, char **start, off_t offset, 467 * int length, int host_no, int inout) 468 * Purpose : Return information about the driver to a user process accessing 469 * the /proc filesystem. 470 * Params : buffer - a buffer to write information to 471 * start - a pointer into this buffer set by this routine to the start 472 * of the required information. 473 * offset - offset into information that we have read upto. 474 * length - length of buffer 475 * host_no - host number to return information for 476 * inout - 0 for reading, 1 for writing. 477 * Returns : length of data written to buffer. 478 */ 479int cumanascsi_2_proc_info (char *buffer, char **start, off_t offset, 480 int length, int host_no, int inout) 481{ 482 int pos, begin; 483 struct Scsi_Host *host = scsi_hostlist; 484 CumanaScsi2_Info *info; 485 Scsi_Device *scd; 486 487 while (host) { 488 if (host->host_no == host_no) 489 break; 490 host = host->next; 491 } 492 if (!host) 493 return 0; 494 495 if (inout == 1) 496 return cumanascsi_2_set_proc_info(host, buffer, length); 497 498 info = (CumanaScsi2_Info *)host->hostdata; 499 500 begin = 0; 501 pos = sprintf(buffer, 502 "Cumana SCSI II driver version %d.%d.%d\n", 503 VER_MAJOR, VER_MINOR, VER_PATCH); 504 505 pos += fas216_print_host(&info->info, buffer + pos); 506 pos += sprintf(buffer + pos, "Term : o%s\n", 507 info->terms ? "n" : "ff"); 508 509 pos += fas216_print_stats(&info->info, buffer + pos); 510 511 pos += sprintf(buffer+pos, "\nAttached devices:\n"); 512 513 for (scd = host->host_queue; scd; scd = scd->next) { 514 int len; 515 516 proc_print_scsidevice(scd, buffer, &len, pos); 517 pos += len; 518 pos += sprintf(buffer+pos, "Extensions: "); 519 if (scd->tagged_supported) 520 pos += sprintf(buffer+pos, "TAG %sabled [%d] ", 521 scd->tagged_queue ? "en" : "dis", 522 scd->current_tag); 523 pos += sprintf(buffer+pos, "\n"); 524 525 if (pos + begin < offset) { 526 begin += pos; 527 pos = 0; 528 } 529 if (pos + begin > offset + length) 530 break; 531 } 532 533 *start = buffer + (offset - begin); 534 pos -= offset - begin; 535 if (pos > length) 536 pos = length; 537 538 return pos; 539} 540 541static Scsi_Host_Template cumanascsi2_template = { 542 module: THIS_MODULE, 543 proc_info: cumanascsi_2_proc_info, 544 name: "Cumana SCSI II", 545 detect: cumanascsi_2_detect, 546 release: cumanascsi_2_release, 547 info: cumanascsi_2_info, 548 bios_param: scsicam_bios_param, 549 can_queue: 1, 550 this_id: 7, 551 sg_tablesize: SG_ALL, 552 cmd_per_lun: 1, 553 use_clustering: DISABLE_CLUSTERING, 554 command: fas216_command, 555 queuecommand: fas216_queue_command, 556 eh_host_reset_handler: fas216_eh_host_reset, 557 eh_bus_reset_handler: fas216_eh_bus_reset, 558 eh_device_reset_handler: fas216_eh_device_reset, 559 eh_abort_handler: fas216_eh_abort, 560 use_new_eh_code: 1 561}; 562 563static int __init cumanascsi2_init(void) 564{ 565 scsi_register_module(MODULE_SCSI_HA, &cumanascsi2_template); 566 if (cumanascsi2_template.present) 567 return 0; 568 569 scsi_unregister_module(MODULE_SCSI_HA, &cumanascsi2_template); 570 return -ENODEV; 571} 572 573static void __exit cumanascsi2_exit(void) 574{ 575 scsi_unregister_module(MODULE_SCSI_HA, &cumanascsi2_template); 576} 577 578module_init(cumanascsi2_init); 579module_exit(cumanascsi2_exit); 580 581MODULE_AUTHOR("Russell King"); 582MODULE_DESCRIPTION("Cumana SCSI-2 driver for Acorn machines"); 583MODULE_PARM(term, "1-8i"); 584MODULE_PARM_DESC(term, "SCSI bus termination"); 585MODULE_LICENSE("GPL"); 586EXPORT_NO_SYMBOLS; 587