diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/scsi/oktagon_esp.c | |
download | op-kernel-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip op-kernel-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/scsi/oktagon_esp.c')
-rw-r--r-- | drivers/scsi/oktagon_esp.c | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/drivers/scsi/oktagon_esp.c b/drivers/scsi/oktagon_esp.c new file mode 100644 index 0000000..573d7ef --- /dev/null +++ b/drivers/scsi/oktagon_esp.c @@ -0,0 +1,609 @@ +/* + * Oktagon_esp.c -- Driver for bsc Oktagon + * + * Written by Carsten Pluntke 1998 + * + * Based on cyber_esp.c + */ + +#include <linux/config.h> + +#if defined(CONFIG_AMIGA) || defined(CONFIG_APUS) +#define USE_BOTTOM_HALF +#endif + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/types.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/blkdev.h> +#include <linux/proc_fs.h> +#include <linux/stat.h> +#include <linux/reboot.h> +#include <asm/system.h> +#include <asm/ptrace.h> +#include <asm/pgtable.h> + + +#include "scsi.h" +#include <scsi/scsi_host.h> +#include "NCR53C9x.h" + +#include <linux/zorro.h> +#include <asm/irq.h> +#include <asm/amigaints.h> +#include <asm/amigahw.h> + +#ifdef USE_BOTTOM_HALF +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#endif + +/* The controller registers can be found in the Z2 config area at these + * offsets: + */ +#define OKTAGON_ESP_ADDR 0x03000 +#define OKTAGON_DMA_ADDR 0x01000 + + +static int dma_bytes_sent(struct NCR_ESP *esp, int fifo_count); +static int dma_can_transfer(struct NCR_ESP *esp, Scsi_Cmnd *sp); +static void dma_dump_state(struct NCR_ESP *esp); +static void dma_init_read(struct NCR_ESP *esp, __u32 vaddress, int length); +static void dma_init_write(struct NCR_ESP *esp, __u32 vaddress, int length); +static void dma_ints_off(struct NCR_ESP *esp); +static void dma_ints_on(struct NCR_ESP *esp); +static int dma_irq_p(struct NCR_ESP *esp); +static void dma_led_off(struct NCR_ESP *esp); +static void dma_led_on(struct NCR_ESP *esp); +static int dma_ports_p(struct NCR_ESP *esp); +static void dma_setup(struct NCR_ESP *esp, __u32 addr, int count, int write); + +static void dma_irq_exit(struct NCR_ESP *esp); +static void dma_invalidate(struct NCR_ESP *esp); + +static void dma_mmu_get_scsi_one(struct NCR_ESP *,Scsi_Cmnd *); +static void dma_mmu_get_scsi_sgl(struct NCR_ESP *,Scsi_Cmnd *); +static void dma_mmu_release_scsi_one(struct NCR_ESP *,Scsi_Cmnd *); +static void dma_mmu_release_scsi_sgl(struct NCR_ESP *,Scsi_Cmnd *); +static void dma_advance_sg(Scsi_Cmnd *); +static int oktagon_notify_reboot(struct notifier_block *this, unsigned long code, void *x); + +#ifdef USE_BOTTOM_HALF +static void dma_commit(void *opaque); + +long oktag_to_io(long *paddr, long *addr, long len); +long oktag_from_io(long *addr, long *paddr, long len); + +static DECLARE_WORK(tq_fake_dma, dma_commit, NULL); + +#define DMA_MAXTRANSFER 0x8000 + +#else + +/* + * No bottom half. Use transfer directly from IRQ. Find a narrow path + * between too much IRQ overhead and clogging the IRQ for too long. + */ + +#define DMA_MAXTRANSFER 0x1000 + +#endif + +static struct notifier_block oktagon_notifier = { + oktagon_notify_reboot, + NULL, + 0 +}; + +static long *paddress; +static long *address; +static long len; +static long dma_on; +static int direction; +static struct NCR_ESP *current_esp; + + +static volatile unsigned char cmd_buffer[16]; + /* This is where all commands are put + * before they are trasfered to the ESP chip + * via PIO. + */ + +/***************************************************************** Detection */ +int oktagon_esp_detect(Scsi_Host_Template *tpnt) +{ + struct NCR_ESP *esp; + struct zorro_dev *z = NULL; + unsigned long address; + struct ESP_regs *eregs; + + while ((z = zorro_find_device(ZORRO_PROD_BSC_OKTAGON_2008, z))) { + unsigned long board = z->resource.start; + if (request_mem_region(board+OKTAGON_ESP_ADDR, + sizeof(struct ESP_regs), "NCR53C9x")) { + /* + * It is a SCSI controller. + * Hardwire Host adapter to SCSI ID 7 + */ + + address = (unsigned long)ZTWO_VADDR(board); + eregs = (struct ESP_regs *)(address + OKTAGON_ESP_ADDR); + + /* This line was 5 lines lower */ + esp = esp_allocate(tpnt, (void *)board+OKTAGON_ESP_ADDR); + + /* we have to shift the registers only one bit for oktagon */ + esp->shift = 1; + + esp_write(eregs->esp_cfg1, (ESP_CONFIG1_PENABLE | 7)); + udelay(5); + if (esp_read(eregs->esp_cfg1) != (ESP_CONFIG1_PENABLE | 7)) + return 0; /* Bail out if address did not hold data */ + + /* Do command transfer with programmed I/O */ + esp->do_pio_cmds = 1; + + /* Required functions */ + esp->dma_bytes_sent = &dma_bytes_sent; + esp->dma_can_transfer = &dma_can_transfer; + esp->dma_dump_state = &dma_dump_state; + esp->dma_init_read = &dma_init_read; + esp->dma_init_write = &dma_init_write; + esp->dma_ints_off = &dma_ints_off; + esp->dma_ints_on = &dma_ints_on; + esp->dma_irq_p = &dma_irq_p; + esp->dma_ports_p = &dma_ports_p; + esp->dma_setup = &dma_setup; + + /* Optional functions */ + esp->dma_barrier = 0; + esp->dma_drain = 0; + esp->dma_invalidate = &dma_invalidate; + esp->dma_irq_entry = 0; + esp->dma_irq_exit = &dma_irq_exit; + esp->dma_led_on = &dma_led_on; + esp->dma_led_off = &dma_led_off; + esp->dma_poll = 0; + esp->dma_reset = 0; + + esp->dma_mmu_get_scsi_one = &dma_mmu_get_scsi_one; + esp->dma_mmu_get_scsi_sgl = &dma_mmu_get_scsi_sgl; + esp->dma_mmu_release_scsi_one = &dma_mmu_release_scsi_one; + esp->dma_mmu_release_scsi_sgl = &dma_mmu_release_scsi_sgl; + esp->dma_advance_sg = &dma_advance_sg; + + /* SCSI chip speed */ + /* Looking at the quartz of the SCSI board... */ + esp->cfreq = 25000000; + + /* The DMA registers on the CyberStorm are mapped + * relative to the device (i.e. in the same Zorro + * I/O block). + */ + esp->dregs = (void *)(address + OKTAGON_DMA_ADDR); + + paddress = (long *) esp->dregs; + + /* ESP register base */ + esp->eregs = eregs; + + /* Set the command buffer */ + esp->esp_command = (volatile unsigned char*) cmd_buffer; + + /* Yes, the virtual address. See below. */ + esp->esp_command_dvma = (__u32) cmd_buffer; + + esp->irq = IRQ_AMIGA_PORTS; + request_irq(IRQ_AMIGA_PORTS, esp_intr, SA_SHIRQ, + "BSC Oktagon SCSI", esp->ehost); + + /* Figure out our scsi ID on the bus */ + esp->scsi_id = 7; + + /* We don't have a differential SCSI-bus. */ + esp->diff = 0; + + esp_initialize(esp); + + printk("ESP_Oktagon Driver 1.1" +#ifdef USE_BOTTOM_HALF + " [BOTTOM_HALF]" +#else + " [IRQ]" +#endif + " registered.\n"); + printk("ESP: Total of %d ESP hosts found, %d actually in use.\n", nesps,esps_in_use); + esps_running = esps_in_use; + current_esp = esp; + register_reboot_notifier(&oktagon_notifier); + return esps_in_use; + } + } + return 0; +} + + +/* + * On certain configurations the SCSI equipment gets confused on reboot, + * so we have to reset it then. + */ + +static int +oktagon_notify_reboot(struct notifier_block *this, unsigned long code, void *x) +{ + struct NCR_ESP *esp; + + if((code == SYS_DOWN || code == SYS_HALT) && (esp = current_esp)) + { + esp_bootup_reset(esp,esp->eregs); + udelay(500); /* Settle time. Maybe unnecessary. */ + } + return NOTIFY_DONE; +} + + + +#ifdef USE_BOTTOM_HALF + + +/* + * The bsc Oktagon controller has no real DMA, so we have to do the 'DMA + * transfer' in the interrupt (Yikes!) or use a bottom half to not to clutter + * IRQ's for longer-than-good. + * + * FIXME + * BIG PROBLEM: 'len' is usually the buffer length, not the expected length + * of the data. So DMA may finish prematurely, further reads lead to + * 'machine check' on APUS systems (don't know about m68k systems, AmigaOS + * deliberately ignores the bus faults) and a normal copy-loop can't + * be exited prematurely just at the right moment by the dma_invalidate IRQ. + * So do it the hard way, write an own copier in assembler and + * catch the exception. + * -- Carsten + */ + + +static void dma_commit(void *opaque) +{ + long wait,len2,pos; + struct NCR_ESP *esp; + + ESPDATA(("Transfer: %ld bytes, Address 0x%08lX, Direction: %d\n", + len,(long) address,direction)); + dma_ints_off(current_esp); + + pos = 0; + wait = 1; + if(direction) /* write? (memory to device) */ + { + while(len > 0) + { + len2 = oktag_to_io(paddress, address+pos, len); + if(!len2) + { + if(wait > 1000) + { + printk("Expedited DMA exit (writing) %ld\n",len); + break; + } + mdelay(wait); + wait *= 2; + } + pos += len2; + len -= len2*sizeof(long); + } + } else { + while(len > 0) + { + len2 = oktag_from_io(address+pos, paddress, len); + if(!len2) + { + if(wait > 1000) + { + printk("Expedited DMA exit (reading) %ld\n",len); + break; + } + mdelay(wait); + wait *= 2; + } + pos += len2; + len -= len2*sizeof(long); + } + } + + /* to make esp->shift work */ + esp=current_esp; + +#if 0 + len2 = (esp_read(current_esp->eregs->esp_tclow) & 0xff) | + ((esp_read(current_esp->eregs->esp_tcmed) & 0xff) << 8); + + /* + * Uh uh. If you see this, len and transfer count registers were out of + * sync. That means really serious trouble. + */ + + if(len2) + printk("Eeeek!! Transfer count still %ld!\n",len2); +#endif + + /* + * Normally we just need to exit and wait for the interrupt to come. + * But at least one device (my Microtek ScanMaker 630) regularly mis- + * calculates the bytes it should send which is really ugly because + * it locks up the SCSI bus if not accounted for. + */ + + if(!(esp_read(current_esp->eregs->esp_status) & ESP_STAT_INTR)) + { + long len = 100; + long trash[10]; + + /* + * Interrupt bit was not set. Either the device is just plain lazy + * so we give it a 10 ms chance or... + */ + while(len-- && (!(esp_read(current_esp->eregs->esp_status) & ESP_STAT_INTR))) + udelay(100); + + + if(!(esp_read(current_esp->eregs->esp_status) & ESP_STAT_INTR)) + { + /* + * So we think that the transfer count is out of sync. Since we + * have all we want we are happy and can ditch the trash. + */ + + len = DMA_MAXTRANSFER; + + while(len-- && (!(esp_read(current_esp->eregs->esp_status) & ESP_STAT_INTR))) + oktag_from_io(trash,paddress,2); + + if(!(esp_read(current_esp->eregs->esp_status) & ESP_STAT_INTR)) + { + /* + * Things really have gone wrong. If we leave the system in that + * state, the SCSI bus is locked forever. I hope that this will + * turn the system in a more or less running state. + */ + printk("Device is bolixed, trying bus reset...\n"); + esp_bootup_reset(current_esp,current_esp->eregs); + } + } + } + + ESPDATA(("Transfer_finale: do_data_finale should come\n")); + + len = 0; + dma_on = 0; + dma_ints_on(current_esp); +} + +#endif + +/************************************************************* DMA Functions */ +static int dma_bytes_sent(struct NCR_ESP *esp, int fifo_count) +{ + /* Since the CyberStorm DMA is fully dedicated to the ESP chip, + * the number of bytes sent (to the ESP chip) equals the number + * of bytes in the FIFO - there is no buffering in the DMA controller. + * XXXX Do I read this right? It is from host to ESP, right? + */ + return fifo_count; +} + +static int dma_can_transfer(struct NCR_ESP *esp, Scsi_Cmnd *sp) +{ + unsigned long sz = sp->SCp.this_residual; + if(sz > DMA_MAXTRANSFER) + sz = DMA_MAXTRANSFER; + return sz; +} + +static void dma_dump_state(struct NCR_ESP *esp) +{ +} + +/* + * What the f$@& is this? + * + * Some SCSI devices (like my Microtek ScanMaker 630 scanner) want to transfer + * more data than requested. How much? Dunno. So ditch the bogus data into + * the sink, hoping the device will advance to the next phase sooner or later. + * + * -- Carsten + */ + +static long oktag_eva_buffer[16]; /* The data sink */ + +static void oktag_check_dma(void) +{ + struct NCR_ESP *esp; + + esp=current_esp; + if(!len) + { + address = oktag_eva_buffer; + len = 2; + /* esp_do_data sets them to zero like len */ + esp_write(current_esp->eregs->esp_tclow,2); + esp_write(current_esp->eregs->esp_tcmed,0); + } +} + +static void dma_init_read(struct NCR_ESP *esp, __u32 vaddress, int length) +{ + /* Zorro is noncached, everything else done using processor. */ + /* cache_clear(addr, length); */ + + if(dma_on) + panic("dma_init_read while dma process is initialized/running!\n"); + direction = 0; + address = (long *) vaddress; + current_esp = esp; + len = length; + oktag_check_dma(); + dma_on = 1; +} + +static void dma_init_write(struct NCR_ESP *esp, __u32 vaddress, int length) +{ + /* cache_push(addr, length); */ + + if(dma_on) + panic("dma_init_write while dma process is initialized/running!\n"); + direction = 1; + address = (long *) vaddress; + current_esp = esp; + len = length; + oktag_check_dma(); + dma_on = 1; +} + +static void dma_ints_off(struct NCR_ESP *esp) +{ + disable_irq(esp->irq); +} + +static void dma_ints_on(struct NCR_ESP *esp) +{ + enable_irq(esp->irq); +} + +static int dma_irq_p(struct NCR_ESP *esp) +{ + /* It's important to check the DMA IRQ bit in the correct way! */ + return (esp_read(esp->eregs->esp_status) & ESP_STAT_INTR); +} + +static void dma_led_off(struct NCR_ESP *esp) +{ +} + +static void dma_led_on(struct NCR_ESP *esp) +{ +} + +static int dma_ports_p(struct NCR_ESP *esp) +{ + return ((custom.intenar) & IF_PORTS); +} + +static void dma_setup(struct NCR_ESP *esp, __u32 addr, int count, int write) +{ + /* On the Sparc, DMA_ST_WRITE means "move data from device to memory" + * so when (write) is true, it actually means READ! + */ + if(write){ + dma_init_read(esp, addr, count); + } else { + dma_init_write(esp, addr, count); + } +} + +/* + * IRQ entry when DMA transfer is ready to be started + */ + +static void dma_irq_exit(struct NCR_ESP *esp) +{ +#ifdef USE_BOTTOM_HALF + if(dma_on) + { + schedule_work(&tq_fake_dma); + } +#else + while(len && !dma_irq_p(esp)) + { + if(direction) + *paddress = *address++; + else + *address++ = *paddress; + len -= (sizeof(long)); + } + len = 0; + dma_on = 0; +#endif +} + +/* + * IRQ entry when DMA has just finished + */ + +static void dma_invalidate(struct NCR_ESP *esp) +{ +} + +/* + * Since the processor does the data transfer we have to use the custom + * mmu interface to pass the virtual address, not the physical. + */ + +void dma_mmu_get_scsi_one(struct NCR_ESP *esp, Scsi_Cmnd *sp) +{ + sp->SCp.ptr = + sp->request_buffer; +} + +void dma_mmu_get_scsi_sgl(struct NCR_ESP *esp, Scsi_Cmnd *sp) +{ + sp->SCp.ptr = page_address(sp->SCp.buffer->page)+ + sp->SCp.buffer->offset; +} + +void dma_mmu_release_scsi_one(struct NCR_ESP *esp, Scsi_Cmnd *sp) +{ +} + +void dma_mmu_release_scsi_sgl(struct NCR_ESP *esp, Scsi_Cmnd *sp) +{ +} + +void dma_advance_sg(Scsi_Cmnd *sp) +{ + sp->SCp.ptr = page_address(sp->SCp.buffer->page)+ + sp->SCp.buffer->offset; +} + + +#define HOSTS_C + +int oktagon_esp_release(struct Scsi_Host *instance) +{ +#ifdef MODULE + unsigned long address = (unsigned long)((struct NCR_ESP *)instance->hostdata)->edev; + esp_release(); + release_mem_region(address, sizeof(struct ESP_regs)); + free_irq(IRQ_AMIGA_PORTS, esp_intr); + unregister_reboot_notifier(&oktagon_notifier); +#endif + return 1; +} + + +static Scsi_Host_Template driver_template = { + .proc_name = "esp-oktagon", + .proc_info = &esp_proc_info, + .name = "BSC Oktagon SCSI", + .detect = oktagon_esp_detect, + .slave_alloc = esp_slave_alloc, + .slave_destroy = esp_slave_destroy, + .release = oktagon_esp_release, + .queuecommand = esp_queue, + .eh_abort_handler = esp_abort, + .eh_bus_reset_handler = esp_reset, + .can_queue = 7, + .this_id = 7, + .sg_tablesize = SG_ALL, + .cmd_per_lun = 1, + .use_clustering = ENABLE_CLUSTERING +}; + + +#include "scsi_module.c" + +MODULE_LICENSE("GPL"); |