| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  *  linux/drivers/acorn/scsi/eesox.c | 
 | 3 |  * | 
 | 4 |  *  Copyright (C) 1997-2005 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 |  *  This driver is based on experimentation.  Hence, it may have made | 
 | 11 |  *  assumptions about the particular card that I have available, and | 
 | 12 |  *  may not be reliable! | 
 | 13 |  * | 
 | 14 |  *  Changelog: | 
 | 15 |  *   01-10-1997	RMK		Created, READONLY version | 
 | 16 |  *   15-02-1998	RMK		READ/WRITE version | 
 | 17 |  *				added DMA support and hardware definitions | 
 | 18 |  *   14-03-1998	RMK		Updated DMA support | 
 | 19 |  *				Added terminator control | 
 | 20 |  *   15-04-1998	RMK		Only do PIO if FAS216 will allow it. | 
 | 21 |  *   27-06-1998	RMK		Changed asm/delay.h to linux/delay.h | 
 | 22 |  *   02-04-2000	RMK	0.0.3	Fixed NO_IRQ/NO_DMA problem, updated for new | 
 | 23 |  *				error handling code. | 
 | 24 |  */ | 
 | 25 | #include <linux/module.h> | 
 | 26 | #include <linux/blkdev.h> | 
 | 27 | #include <linux/kernel.h> | 
 | 28 | #include <linux/string.h> | 
 | 29 | #include <linux/ioport.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 30 | #include <linux/proc_fs.h> | 
 | 31 | #include <linux/delay.h> | 
 | 32 | #include <linux/interrupt.h> | 
 | 33 | #include <linux/init.h> | 
 | 34 | #include <linux/dma-mapping.h> | 
 | 35 |  | 
 | 36 | #include <asm/io.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 37 | #include <asm/dma.h> | 
 | 38 | #include <asm/ecard.h> | 
 | 39 | #include <asm/pgtable.h> | 
 | 40 |  | 
 | 41 | #include "../scsi.h" | 
 | 42 | #include <scsi/scsi_host.h> | 
 | 43 | #include "fas216.h" | 
 | 44 | #include "scsi.h" | 
 | 45 |  | 
 | 46 | #include <scsi/scsicam.h> | 
 | 47 |  | 
 | 48 | #define EESOX_FAS216_OFFSET	0x3000 | 
 | 49 | #define EESOX_FAS216_SHIFT	5 | 
 | 50 |  | 
 | 51 | #define EESOX_DMASTAT		0x2800 | 
 | 52 | #define EESOX_STAT_INTR		0x01 | 
 | 53 | #define EESOX_STAT_DMA		0x02 | 
 | 54 |  | 
 | 55 | #define EESOX_CONTROL		0x2800 | 
 | 56 | #define EESOX_INTR_ENABLE	0x04 | 
 | 57 | #define EESOX_TERM_ENABLE	0x02 | 
 | 58 | #define EESOX_RESET		0x01 | 
 | 59 |  | 
 | 60 | #define EESOX_DMADATA		0x3800 | 
 | 61 |  | 
 | 62 | #define VERSION "1.10 (17/01/2003 2.5.59)" | 
 | 63 |  | 
 | 64 | /* | 
 | 65 |  * Use term=0,1,0,0,0 to turn terminators on/off | 
 | 66 |  */ | 
 | 67 | static int term[MAX_ECARDS] = { 1, 1, 1, 1, 1, 1, 1, 1 }; | 
 | 68 |  | 
 | 69 | #define NR_SG	256 | 
 | 70 |  | 
 | 71 | struct eesoxscsi_info { | 
 | 72 | 	FAS216_Info		info; | 
 | 73 | 	struct expansion_card	*ec; | 
 | 74 | 	void __iomem		*base; | 
 | 75 | 	void __iomem		*ctl_port; | 
 | 76 | 	unsigned int		control; | 
 | 77 | 	struct scatterlist	sg[NR_SG];	/* Scatter DMA list	*/ | 
 | 78 | }; | 
 | 79 |  | 
 | 80 | /* Prototype: void eesoxscsi_irqenable(ec, irqnr) | 
 | 81 |  * Purpose  : Enable interrupts on EESOX SCSI card | 
 | 82 |  * Params   : ec    - expansion card structure | 
 | 83 |  *          : irqnr - interrupt number | 
 | 84 |  */ | 
 | 85 | static void | 
 | 86 | eesoxscsi_irqenable(struct expansion_card *ec, int irqnr) | 
 | 87 | { | 
 | 88 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data; | 
 | 89 |  | 
 | 90 | 	info->control |= EESOX_INTR_ENABLE; | 
 | 91 |  | 
 | 92 | 	writeb(info->control, info->ctl_port); | 
 | 93 | } | 
 | 94 |  | 
 | 95 | /* Prototype: void eesoxscsi_irqdisable(ec, irqnr) | 
 | 96 |  * Purpose  : Disable interrupts on EESOX SCSI card | 
 | 97 |  * Params   : ec    - expansion card structure | 
 | 98 |  *          : irqnr - interrupt number | 
 | 99 |  */ | 
 | 100 | static void | 
 | 101 | eesoxscsi_irqdisable(struct expansion_card *ec, int irqnr) | 
 | 102 | { | 
 | 103 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)ec->irq_data; | 
 | 104 |  | 
 | 105 | 	info->control &= ~EESOX_INTR_ENABLE; | 
 | 106 |  | 
 | 107 | 	writeb(info->control, info->ctl_port); | 
 | 108 | } | 
 | 109 |  | 
 | 110 | static const expansioncard_ops_t eesoxscsi_ops = { | 
 | 111 | 	.irqenable	= eesoxscsi_irqenable, | 
 | 112 | 	.irqdisable	= eesoxscsi_irqdisable, | 
 | 113 | }; | 
 | 114 |  | 
 | 115 | /* Prototype: void eesoxscsi_terminator_ctl(*host, on_off) | 
 | 116 |  * Purpose  : Turn the EESOX SCSI terminators on or off | 
 | 117 |  * Params   : host   - card to turn on/off | 
 | 118 |  *          : on_off - !0 to turn on, 0 to turn off | 
 | 119 |  */ | 
 | 120 | static void | 
 | 121 | eesoxscsi_terminator_ctl(struct Scsi_Host *host, int on_off) | 
 | 122 | { | 
 | 123 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 124 | 	unsigned long flags; | 
 | 125 |  | 
 | 126 | 	spin_lock_irqsave(host->host_lock, flags); | 
 | 127 | 	if (on_off) | 
 | 128 | 		info->control |= EESOX_TERM_ENABLE; | 
 | 129 | 	else | 
 | 130 | 		info->control &= ~EESOX_TERM_ENABLE; | 
 | 131 |  | 
 | 132 | 	writeb(info->control, info->ctl_port); | 
 | 133 | 	spin_unlock_irqrestore(host->host_lock, flags); | 
 | 134 | } | 
 | 135 |  | 
 | 136 | /* Prototype: void eesoxscsi_intr(irq, *dev_id, *regs) | 
 | 137 |  * Purpose  : handle interrupts from EESOX SCSI card | 
 | 138 |  * Params   : irq    - interrupt number | 
 | 139 |  *	      dev_id - user-defined (Scsi_Host structure) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 140 |  */ | 
 | 141 | static irqreturn_t | 
| David Howells | 7d12e78 | 2006-10-05 14:55:46 +0100 | [diff] [blame] | 142 | eesoxscsi_intr(int irq, void *dev_id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 143 | { | 
 | 144 | 	struct eesoxscsi_info *info = dev_id; | 
 | 145 |  | 
 | 146 | 	return fas216_intr(&info->info); | 
 | 147 | } | 
 | 148 |  | 
 | 149 | /* Prototype: fasdmatype_t eesoxscsi_dma_setup(host, SCpnt, direction, min_type) | 
 | 150 |  * Purpose  : initialises DMA/PIO | 
 | 151 |  * Params   : host      - host | 
 | 152 |  *	      SCpnt     - command | 
 | 153 |  *	      direction - DMA on to/off of card | 
 | 154 |  *	      min_type  - minimum DMA support that we must have for this transfer | 
 | 155 |  * Returns  : type of transfer to be performed | 
 | 156 |  */ | 
 | 157 | static fasdmatype_t | 
| Christoph Hellwig | 0a04137 | 2005-10-31 18:31:56 +0100 | [diff] [blame] | 158 | eesoxscsi_dma_setup(struct Scsi_Host *host, struct scsi_pointer *SCp, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 159 | 		       fasdmadir_t direction, fasdmatype_t min_type) | 
 | 160 | { | 
 | 161 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 162 | 	struct device *dev = scsi_get_device(host); | 
 | 163 | 	int dmach = info->info.scsi.dma; | 
 | 164 |  | 
 | 165 | 	if (dmach != NO_DMA && | 
 | 166 | 	    (min_type == fasdma_real_all || SCp->this_residual >= 512)) { | 
 | 167 | 		int bufs, map_dir, dma_dir; | 
 | 168 |  | 
 | 169 | 		bufs = copy_SCp_to_sg(&info->sg[0], SCp, NR_SG); | 
 | 170 |  | 
 | 171 | 		if (direction == DMA_OUT) | 
 | 172 | 			map_dir = DMA_TO_DEVICE, | 
 | 173 | 			dma_dir = DMA_MODE_WRITE; | 
 | 174 | 		else | 
 | 175 | 			map_dir = DMA_FROM_DEVICE, | 
 | 176 | 			dma_dir = DMA_MODE_READ; | 
 | 177 |  | 
| Russell King | 23d046f | 2007-03-04 20:21:38 +0000 | [diff] [blame] | 178 | 		dma_map_sg(dev, info->sg, bufs, map_dir); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 179 |  | 
 | 180 | 		disable_dma(dmach); | 
| Russell King | 23d046f | 2007-03-04 20:21:38 +0000 | [diff] [blame] | 181 | 		set_dma_sg(dmach, info->sg, bufs); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 182 | 		set_dma_mode(dmach, dma_dir); | 
 | 183 | 		enable_dma(dmach); | 
 | 184 | 		return fasdma_real_all; | 
 | 185 | 	} | 
 | 186 | 	/* | 
 | 187 | 	 * We don't do DMA, we only do slow PIO | 
 | 188 | 	 * | 
 | 189 | 	 * Some day, we will do Pseudo DMA | 
 | 190 | 	 */ | 
 | 191 | 	return fasdma_pseudo; | 
 | 192 | } | 
 | 193 |  | 
 | 194 | static void eesoxscsi_buffer_in(void *buf, int length, void __iomem *base) | 
 | 195 | { | 
 | 196 | 	const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET; | 
 | 197 | 	const void __iomem *reg_dmastat = base + EESOX_DMASTAT; | 
 | 198 | 	const void __iomem *reg_dmadata = base + EESOX_DMADATA; | 
| Tobias Klauser | c5a69d5 | 2007-02-17 20:11:19 +0100 | [diff] [blame] | 199 | 	register const unsigned long mask = 0xffff; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 200 |  | 
 | 201 | 	do { | 
 | 202 | 		unsigned int status; | 
 | 203 |  | 
 | 204 | 		/* | 
 | 205 | 		 * Interrupt request? | 
 | 206 | 		 */ | 
 | 207 | 		status = readb(reg_fas + (REG_STAT << EESOX_FAS216_SHIFT)); | 
 | 208 | 		if (status & STAT_INT) | 
 | 209 | 			break; | 
 | 210 |  | 
 | 211 | 		/* | 
 | 212 | 		 * DMA request active? | 
 | 213 | 		 */ | 
 | 214 | 		status = readb(reg_dmastat); | 
 | 215 | 		if (!(status & EESOX_STAT_DMA)) | 
 | 216 | 			continue; | 
 | 217 |  | 
 | 218 | 		/* | 
 | 219 | 		 * Get number of bytes in FIFO | 
 | 220 | 		 */ | 
 | 221 | 		status = readb(reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF; | 
 | 222 | 		if (status > 16) | 
 | 223 | 			status = 16; | 
 | 224 | 		if (status > length) | 
 | 225 | 			status = length; | 
 | 226 |  | 
 | 227 | 		/* | 
 | 228 | 		 * Align buffer. | 
 | 229 | 		 */ | 
 | 230 | 		if (((u32)buf) & 2 && status >= 2) { | 
 | 231 | 			*(u16 *)buf = readl(reg_dmadata); | 
 | 232 | 			buf += 2; | 
 | 233 | 			status -= 2; | 
 | 234 | 			length -= 2; | 
 | 235 | 		} | 
 | 236 |  | 
 | 237 | 		if (status >= 8) { | 
 | 238 | 			unsigned long l1, l2; | 
 | 239 |  | 
 | 240 | 			l1 = readl(reg_dmadata) & mask; | 
 | 241 | 			l1 |= readl(reg_dmadata) << 16; | 
 | 242 | 			l2 = readl(reg_dmadata) & mask; | 
 | 243 | 			l2 |= readl(reg_dmadata) << 16; | 
 | 244 | 			*(u32 *)buf = l1; | 
 | 245 | 			buf += 4; | 
 | 246 | 			*(u32 *)buf = l2; | 
 | 247 | 			buf += 4; | 
 | 248 | 			length -= 8; | 
 | 249 | 			continue; | 
 | 250 | 		} | 
 | 251 |  | 
 | 252 | 		if (status >= 4) { | 
 | 253 | 			unsigned long l1; | 
 | 254 |  | 
 | 255 | 			l1 = readl(reg_dmadata) & mask; | 
 | 256 | 			l1 |= readl(reg_dmadata) << 16; | 
 | 257 |  | 
 | 258 | 			*(u32 *)buf = l1; | 
 | 259 | 			buf += 4; | 
 | 260 | 			length -= 4; | 
 | 261 | 			continue; | 
 | 262 | 		} | 
 | 263 |  | 
 | 264 | 		if (status >= 2) { | 
 | 265 | 			*(u16 *)buf = readl(reg_dmadata); | 
 | 266 | 			buf += 2; | 
 | 267 | 			length -= 2; | 
 | 268 | 		} | 
 | 269 | 	} while (length); | 
 | 270 | } | 
 | 271 |  | 
 | 272 | static void eesoxscsi_buffer_out(void *buf, int length, void __iomem *base) | 
 | 273 | { | 
 | 274 | 	const void __iomem *reg_fas = base + EESOX_FAS216_OFFSET; | 
 | 275 | 	const void __iomem *reg_dmastat = base + EESOX_DMASTAT; | 
 | 276 | 	const void __iomem *reg_dmadata = base + EESOX_DMADATA; | 
 | 277 |  | 
 | 278 | 	do { | 
 | 279 | 		unsigned int status; | 
 | 280 |  | 
 | 281 | 		/* | 
 | 282 | 		 * Interrupt request? | 
 | 283 | 		 */ | 
 | 284 | 		status = readb(reg_fas + (REG_STAT << EESOX_FAS216_SHIFT)); | 
 | 285 | 		if (status & STAT_INT) | 
 | 286 | 			break; | 
 | 287 |  | 
 | 288 | 		/* | 
 | 289 | 		 * DMA request active? | 
 | 290 | 		 */ | 
 | 291 | 		status = readb(reg_dmastat); | 
 | 292 | 		if (!(status & EESOX_STAT_DMA)) | 
 | 293 | 			continue; | 
 | 294 |  | 
 | 295 | 		/* | 
 | 296 | 		 * Get number of bytes in FIFO | 
 | 297 | 		 */ | 
 | 298 | 		status = readb(reg_fas + (REG_CFIS << EESOX_FAS216_SHIFT)) & CFIS_CF; | 
 | 299 | 		if (status > 16) | 
 | 300 | 			status = 16; | 
 | 301 | 		status = 16 - status; | 
 | 302 | 		if (status > length) | 
 | 303 | 			status = length; | 
 | 304 | 		status &= ~1; | 
 | 305 |  | 
 | 306 | 		/* | 
 | 307 | 		 * Align buffer. | 
 | 308 | 		 */ | 
 | 309 | 		if (((u32)buf) & 2 && status >= 2) { | 
 | 310 | 			writel(*(u16 *)buf << 16, reg_dmadata); | 
 | 311 | 			buf += 2; | 
 | 312 | 			status -= 2; | 
 | 313 | 			length -= 2; | 
 | 314 | 		} | 
 | 315 |  | 
 | 316 | 		if (status >= 8) { | 
 | 317 | 			unsigned long l1, l2; | 
 | 318 |  | 
 | 319 | 			l1 = *(u32 *)buf; | 
 | 320 | 			buf += 4; | 
 | 321 | 			l2 = *(u32 *)buf; | 
 | 322 | 			buf += 4; | 
 | 323 |  | 
 | 324 | 			writel(l1 << 16, reg_dmadata); | 
 | 325 | 			writel(l1, reg_dmadata); | 
 | 326 | 			writel(l2 << 16, reg_dmadata); | 
 | 327 | 			writel(l2, reg_dmadata); | 
 | 328 | 			length -= 8; | 
 | 329 | 			continue; | 
 | 330 | 		} | 
 | 331 |  | 
 | 332 | 		if (status >= 4) { | 
 | 333 | 			unsigned long l1; | 
 | 334 |  | 
 | 335 | 			l1 = *(u32 *)buf; | 
 | 336 | 			buf += 4; | 
 | 337 |  | 
 | 338 | 			writel(l1 << 16, reg_dmadata); | 
 | 339 | 			writel(l1, reg_dmadata); | 
 | 340 | 			length -= 4; | 
 | 341 | 			continue; | 
 | 342 | 		} | 
 | 343 |  | 
 | 344 | 		if (status >= 2) { | 
 | 345 | 			writel(*(u16 *)buf << 16, reg_dmadata); | 
 | 346 | 			buf += 2; | 
 | 347 | 			length -= 2; | 
 | 348 | 		} | 
 | 349 | 	} while (length); | 
 | 350 | } | 
 | 351 |  | 
 | 352 | static void | 
| Christoph Hellwig | 0a04137 | 2005-10-31 18:31:56 +0100 | [diff] [blame] | 353 | eesoxscsi_dma_pseudo(struct Scsi_Host *host, struct scsi_pointer *SCp, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 354 | 		     fasdmadir_t dir, int transfer_size) | 
 | 355 | { | 
 | 356 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 357 | 	if (dir == DMA_IN) { | 
 | 358 | 		eesoxscsi_buffer_in(SCp->ptr, SCp->this_residual, info->base); | 
 | 359 | 	} else { | 
 | 360 | 		eesoxscsi_buffer_out(SCp->ptr, SCp->this_residual, info->base); | 
 | 361 | 	} | 
 | 362 | } | 
 | 363 |  | 
 | 364 | /* Prototype: int eesoxscsi_dma_stop(host, SCpnt) | 
 | 365 |  * Purpose  : stops DMA/PIO | 
 | 366 |  * Params   : host  - host | 
 | 367 |  *	      SCpnt - command | 
 | 368 |  */ | 
 | 369 | static void | 
| Christoph Hellwig | 0a04137 | 2005-10-31 18:31:56 +0100 | [diff] [blame] | 370 | eesoxscsi_dma_stop(struct Scsi_Host *host, struct scsi_pointer *SCp) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 371 | { | 
 | 372 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 373 | 	if (info->info.scsi.dma != NO_DMA) | 
 | 374 | 		disable_dma(info->info.scsi.dma); | 
 | 375 | } | 
 | 376 |  | 
 | 377 | /* Prototype: const char *eesoxscsi_info(struct Scsi_Host * host) | 
 | 378 |  * Purpose  : returns a descriptive string about this interface, | 
 | 379 |  * Params   : host - driver host structure to return info for. | 
 | 380 |  * Returns  : pointer to a static buffer containing null terminated string. | 
 | 381 |  */ | 
 | 382 | const char *eesoxscsi_info(struct Scsi_Host *host) | 
 | 383 | { | 
 | 384 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 385 | 	static char string[150]; | 
 | 386 |  | 
 | 387 | 	sprintf(string, "%s (%s) in slot %d v%s terminators o%s", | 
 | 388 | 		host->hostt->name, info->info.scsi.type, info->ec->slot_no, | 
 | 389 | 		VERSION, info->control & EESOX_TERM_ENABLE ? "n" : "ff"); | 
 | 390 |  | 
 | 391 | 	return string; | 
 | 392 | } | 
 | 393 |  | 
 | 394 | /* Prototype: int eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length) | 
 | 395 |  * Purpose  : Set a driver specific function | 
 | 396 |  * Params   : host   - host to setup | 
 | 397 |  *          : buffer - buffer containing string describing operation | 
 | 398 |  *          : length - length of string | 
 | 399 |  * Returns  : -EINVAL, or 0 | 
 | 400 |  */ | 
 | 401 | static int | 
 | 402 | eesoxscsi_set_proc_info(struct Scsi_Host *host, char *buffer, int length) | 
 | 403 | { | 
 | 404 | 	int ret = length; | 
 | 405 |  | 
 | 406 | 	if (length >= 9 && strncmp(buffer, "EESOXSCSI", 9) == 0) { | 
 | 407 | 		buffer += 9; | 
 | 408 | 		length -= 9; | 
 | 409 |  | 
 | 410 | 		if (length >= 5 && strncmp(buffer, "term=", 5) == 0) { | 
 | 411 | 			if (buffer[5] == '1') | 
 | 412 | 				eesoxscsi_terminator_ctl(host, 1); | 
 | 413 | 			else if (buffer[5] == '0') | 
 | 414 | 				eesoxscsi_terminator_ctl(host, 0); | 
 | 415 | 			else | 
 | 416 | 				ret = -EINVAL; | 
 | 417 | 		} else | 
 | 418 | 			ret = -EINVAL; | 
 | 419 | 	} else | 
 | 420 | 		ret = -EINVAL; | 
 | 421 |  | 
 | 422 | 	return ret; | 
 | 423 | } | 
 | 424 |  | 
 | 425 | /* Prototype: int eesoxscsi_proc_info(char *buffer, char **start, off_t offset, | 
 | 426 |  *				      int length, int host_no, int inout) | 
 | 427 |  * Purpose  : Return information about the driver to a user process accessing | 
 | 428 |  *	      the /proc filesystem. | 
 | 429 |  * Params   : buffer - a buffer to write information to | 
 | 430 |  *	      start  - a pointer into this buffer set by this routine to the start | 
 | 431 |  *		       of the required information. | 
 | 432 |  *	      offset - offset into information that we have read upto. | 
 | 433 |  *	      length - length of buffer | 
 | 434 |  *	      host_no - host number to return information for | 
 | 435 |  *	      inout  - 0 for reading, 1 for writing. | 
 | 436 |  * Returns  : length of data written to buffer. | 
 | 437 |  */ | 
 | 438 | int eesoxscsi_proc_info(struct Scsi_Host *host, char *buffer, char **start, off_t offset, | 
 | 439 | 			    int length, int inout) | 
 | 440 | { | 
 | 441 | 	struct eesoxscsi_info *info; | 
 | 442 | 	char *p = buffer; | 
 | 443 | 	int pos; | 
 | 444 |  | 
 | 445 | 	if (inout == 1) | 
 | 446 | 		return eesoxscsi_set_proc_info(host, buffer, length); | 
 | 447 |  | 
 | 448 | 	info = (struct eesoxscsi_info *)host->hostdata; | 
 | 449 |  | 
 | 450 | 	p += sprintf(p, "EESOX SCSI driver v%s\n", VERSION); | 
 | 451 | 	p += fas216_print_host(&info->info, p); | 
 | 452 | 	p += sprintf(p, "Term    : o%s\n", | 
 | 453 | 			info->control & EESOX_TERM_ENABLE ? "n" : "ff"); | 
 | 454 |  | 
 | 455 | 	p += fas216_print_stats(&info->info, p); | 
 | 456 | 	p += fas216_print_devices(&info->info, p); | 
 | 457 |  | 
 | 458 | 	*start = buffer + offset; | 
 | 459 | 	pos = p - buffer - offset; | 
 | 460 | 	if (pos > length) | 
 | 461 | 		pos = length; | 
 | 462 |  | 
 | 463 | 	return pos; | 
 | 464 | } | 
 | 465 |  | 
| Yani Ioannou | 10523b3 | 2005-05-17 06:43:37 -0400 | [diff] [blame] | 466 | static ssize_t eesoxscsi_show_term(struct device *dev, struct device_attribute *attr, char *buf) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 467 | { | 
 | 468 | 	struct expansion_card *ec = ECARD_DEV(dev); | 
 | 469 | 	struct Scsi_Host *host = ecard_get_drvdata(ec); | 
 | 470 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 471 |  | 
 | 472 | 	return sprintf(buf, "%d\n", info->control & EESOX_TERM_ENABLE ? 1 : 0); | 
 | 473 | } | 
 | 474 |  | 
| Yani Ioannou | 10523b3 | 2005-05-17 06:43:37 -0400 | [diff] [blame] | 475 | static ssize_t eesoxscsi_store_term(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 476 | { | 
 | 477 | 	struct expansion_card *ec = ECARD_DEV(dev); | 
 | 478 | 	struct Scsi_Host *host = ecard_get_drvdata(ec); | 
 | 479 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 480 | 	unsigned long flags; | 
 | 481 |  | 
 | 482 | 	if (len > 1) { | 
 | 483 | 		spin_lock_irqsave(host->host_lock, flags); | 
 | 484 | 		if (buf[0] != '0') { | 
 | 485 | 			info->control |= EESOX_TERM_ENABLE; | 
 | 486 | 		} else { | 
 | 487 | 			info->control &= ~EESOX_TERM_ENABLE; | 
 | 488 | 		} | 
 | 489 | 		writeb(info->control, info->ctl_port); | 
 | 490 | 		spin_unlock_irqrestore(host->host_lock, flags); | 
 | 491 | 	} | 
 | 492 |  | 
 | 493 | 	return len; | 
 | 494 | } | 
 | 495 |  | 
 | 496 | static DEVICE_ATTR(bus_term, S_IRUGO | S_IWUSR, | 
 | 497 | 		   eesoxscsi_show_term, eesoxscsi_store_term); | 
 | 498 |  | 
| Christoph Hellwig | d0be4a7d | 2005-10-31 18:31:40 +0100 | [diff] [blame] | 499 | static struct scsi_host_template eesox_template = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 500 | 	.module				= THIS_MODULE, | 
 | 501 | 	.proc_info			= eesoxscsi_proc_info, | 
 | 502 | 	.name				= "EESOX SCSI", | 
 | 503 | 	.info				= eesoxscsi_info, | 
 | 504 | 	.queuecommand			= fas216_queue_command, | 
 | 505 | 	.eh_host_reset_handler		= fas216_eh_host_reset, | 
 | 506 | 	.eh_bus_reset_handler		= fas216_eh_bus_reset, | 
 | 507 | 	.eh_device_reset_handler	= fas216_eh_device_reset, | 
 | 508 | 	.eh_abort_handler		= fas216_eh_abort, | 
 | 509 | 	.can_queue			= 1, | 
 | 510 | 	.this_id			= 7, | 
| Russell King | 5369bea | 2008-12-11 16:37:06 +0000 | [diff] [blame] | 511 | 	.sg_tablesize			= SCSI_MAX_SG_CHAIN_SEGMENTS, | 
 | 512 | 	.dma_boundary			= IOMD_DMA_BOUNDARY, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 513 | 	.cmd_per_lun			= 1, | 
 | 514 | 	.use_clustering			= DISABLE_CLUSTERING, | 
 | 515 | 	.proc_name			= "eesox", | 
 | 516 | }; | 
 | 517 |  | 
 | 518 | static int __devinit | 
 | 519 | eesoxscsi_probe(struct expansion_card *ec, const struct ecard_id *id) | 
 | 520 | { | 
 | 521 | 	struct Scsi_Host *host; | 
 | 522 | 	struct eesoxscsi_info *info; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 523 | 	void __iomem *base; | 
 | 524 | 	int ret; | 
 | 525 |  | 
 | 526 | 	ret = ecard_request_resources(ec); | 
 | 527 | 	if (ret) | 
 | 528 | 		goto out; | 
 | 529 |  | 
| Russell King | 10bdaaa | 2007-05-10 18:40:51 +0100 | [diff] [blame] | 530 | 	base = ecardm_iomap(ec, ECARD_RES_IOCFAST, 0, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 531 | 	if (!base) { | 
 | 532 | 		ret = -ENOMEM; | 
 | 533 | 		goto out_region; | 
 | 534 | 	} | 
 | 535 |  | 
 | 536 | 	host = scsi_host_alloc(&eesox_template, | 
 | 537 | 			       sizeof(struct eesoxscsi_info)); | 
 | 538 | 	if (!host) { | 
 | 539 | 		ret = -ENOMEM; | 
| Russell King | 10bdaaa | 2007-05-10 18:40:51 +0100 | [diff] [blame] | 540 | 		goto out_region; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 541 | 	} | 
 | 542 |  | 
 | 543 | 	ecard_set_drvdata(ec, host); | 
 | 544 |  | 
 | 545 | 	info = (struct eesoxscsi_info *)host->hostdata; | 
 | 546 | 	info->ec	= ec; | 
 | 547 | 	info->base	= base; | 
 | 548 | 	info->ctl_port	= base + EESOX_CONTROL; | 
 | 549 | 	info->control	= term[ec->slot_no] ? EESOX_TERM_ENABLE : 0; | 
 | 550 | 	writeb(info->control, info->ctl_port); | 
 | 551 |  | 
 | 552 | 	info->info.scsi.io_base		= base + EESOX_FAS216_OFFSET; | 
 | 553 | 	info->info.scsi.io_shift	= EESOX_FAS216_SHIFT; | 
 | 554 | 	info->info.scsi.irq		= ec->irq; | 
 | 555 | 	info->info.scsi.dma		= ec->dma; | 
 | 556 | 	info->info.ifcfg.clockrate	= 40; /* MHz */ | 
 | 557 | 	info->info.ifcfg.select_timeout	= 255; | 
 | 558 | 	info->info.ifcfg.asyncperiod	= 200; /* ns */ | 
 | 559 | 	info->info.ifcfg.sync_max_depth	= 7; | 
 | 560 | 	info->info.ifcfg.cntl3		= CNTL3_FASTSCSI | CNTL3_FASTCLK; | 
 | 561 | 	info->info.ifcfg.disconnect_ok	= 1; | 
 | 562 | 	info->info.ifcfg.wide_max_size	= 0; | 
 | 563 | 	info->info.ifcfg.capabilities	= FASCAP_PSEUDODMA; | 
 | 564 | 	info->info.dma.setup		= eesoxscsi_dma_setup; | 
 | 565 | 	info->info.dma.pseudo		= eesoxscsi_dma_pseudo; | 
 | 566 | 	info->info.dma.stop		= eesoxscsi_dma_stop; | 
 | 567 |  | 
 | 568 | 	ec->irqaddr	= base + EESOX_DMASTAT; | 
 | 569 | 	ec->irqmask	= EESOX_STAT_INTR; | 
| Russell King | c7b87f3 | 2007-05-10 16:46:13 +0100 | [diff] [blame] | 570 |  | 
 | 571 | 	ecard_setirq(ec, &eesoxscsi_ops, info); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 572 |  | 
 | 573 | 	device_create_file(&ec->dev, &dev_attr_bus_term); | 
 | 574 |  | 
 | 575 | 	ret = fas216_init(host); | 
 | 576 | 	if (ret) | 
 | 577 | 		goto out_free; | 
 | 578 |  | 
 | 579 | 	ret = request_irq(ec->irq, eesoxscsi_intr, 0, "eesoxscsi", info); | 
 | 580 | 	if (ret) { | 
 | 581 | 		printk("scsi%d: IRQ%d not free: %d\n", | 
 | 582 | 		       host->host_no, ec->irq, ret); | 
 | 583 | 		goto out_remove; | 
 | 584 | 	} | 
 | 585 |  | 
 | 586 | 	if (info->info.scsi.dma != NO_DMA) { | 
 | 587 | 		if (request_dma(info->info.scsi.dma, "eesox")) { | 
 | 588 | 			printk("scsi%d: DMA%d not free, DMA disabled\n", | 
 | 589 | 			       host->host_no, info->info.scsi.dma); | 
 | 590 | 			info->info.scsi.dma = NO_DMA; | 
 | 591 | 		} else { | 
 | 592 | 			set_dma_speed(info->info.scsi.dma, 180); | 
 | 593 | 			info->info.ifcfg.capabilities |= FASCAP_DMA; | 
 | 594 | 			info->info.ifcfg.cntl3 |= CNTL3_BS8; | 
 | 595 | 		} | 
 | 596 | 	} | 
 | 597 |  | 
 | 598 | 	ret = fas216_add(host, &ec->dev); | 
 | 599 | 	if (ret == 0) | 
 | 600 | 		goto out; | 
 | 601 |  | 
 | 602 | 	if (info->info.scsi.dma != NO_DMA) | 
 | 603 | 		free_dma(info->info.scsi.dma); | 
 | 604 | 	free_irq(ec->irq, host); | 
 | 605 |  | 
 | 606 |  out_remove: | 
 | 607 | 	fas216_remove(host); | 
 | 608 |  | 
 | 609 |  out_free: | 
 | 610 | 	device_remove_file(&ec->dev, &dev_attr_bus_term); | 
 | 611 | 	scsi_host_put(host); | 
 | 612 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 613 |  out_region: | 
 | 614 | 	ecard_release_resources(ec); | 
 | 615 |  | 
 | 616 |  out: | 
 | 617 | 	return ret; | 
 | 618 | } | 
 | 619 |  | 
 | 620 | static void __devexit eesoxscsi_remove(struct expansion_card *ec) | 
 | 621 | { | 
 | 622 | 	struct Scsi_Host *host = ecard_get_drvdata(ec); | 
 | 623 | 	struct eesoxscsi_info *info = (struct eesoxscsi_info *)host->hostdata; | 
 | 624 |  | 
 | 625 | 	ecard_set_drvdata(ec, NULL); | 
 | 626 | 	fas216_remove(host); | 
 | 627 |  | 
 | 628 | 	if (info->info.scsi.dma != NO_DMA) | 
 | 629 | 		free_dma(info->info.scsi.dma); | 
 | 630 | 	free_irq(ec->irq, info); | 
 | 631 |  | 
 | 632 | 	device_remove_file(&ec->dev, &dev_attr_bus_term); | 
 | 633 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 634 | 	fas216_release(host); | 
 | 635 | 	scsi_host_put(host); | 
 | 636 | 	ecard_release_resources(ec); | 
 | 637 | } | 
 | 638 |  | 
 | 639 | static const struct ecard_id eesoxscsi_cids[] = { | 
 | 640 | 	{ MANU_EESOX, PROD_EESOX_SCSI2 }, | 
 | 641 | 	{ 0xffff, 0xffff }, | 
 | 642 | }; | 
 | 643 |  | 
 | 644 | static struct ecard_driver eesoxscsi_driver = { | 
 | 645 | 	.probe		= eesoxscsi_probe, | 
 | 646 | 	.remove		= __devexit_p(eesoxscsi_remove), | 
 | 647 | 	.id_table	= eesoxscsi_cids, | 
 | 648 | 	.drv = { | 
 | 649 | 		.name		= "eesoxscsi", | 
 | 650 | 	}, | 
 | 651 | }; | 
 | 652 |  | 
 | 653 | static int __init eesox_init(void) | 
 | 654 | { | 
 | 655 | 	return ecard_register_driver(&eesoxscsi_driver); | 
 | 656 | } | 
 | 657 |  | 
 | 658 | static void __exit eesox_exit(void) | 
 | 659 | { | 
 | 660 | 	ecard_remove_driver(&eesoxscsi_driver); | 
 | 661 | } | 
 | 662 |  | 
 | 663 | module_init(eesox_init); | 
 | 664 | module_exit(eesox_exit); | 
 | 665 |  | 
 | 666 | MODULE_AUTHOR("Russell King"); | 
 | 667 | MODULE_DESCRIPTION("EESOX 'Fast' SCSI driver for Acorn machines"); | 
| Rusty Russell | 8d3b33f | 2006-03-25 03:07:05 -0800 | [diff] [blame] | 668 | module_param_array(term, int, NULL, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 669 | MODULE_PARM_DESC(term, "SCSI bus termination"); | 
 | 670 | MODULE_LICENSE("GPL"); |