Merge branch 'linus' into timers/hpet
[linux-2.6] / drivers / scsi / sun3x_esp.c
1 /* sun3x_esp.c: ESP front-end for Sun3x systems.
2  *
3  * Copyright (C) 2007,2008 Thomas Bogendoerfer (tsbogend@alpha.franken.de)
4  */
5
6 #include <linux/kernel.h>
7 #include <linux/types.h>
8 #include <linux/delay.h>
9 #include <linux/module.h>
10 #include <linux/init.h>
11 #include <linux/platform_device.h>
12 #include <linux/dma-mapping.h>
13 #include <linux/interrupt.h>
14
15 #include <asm/sun3x.h>
16 #include <asm/io.h>
17 #include <asm/dma.h>
18 #include <asm/dvma.h>
19
20 /* DMA controller reg offsets */
21 #define DMA_CSR         0x00UL  /* rw  DMA control/status register    0x00   */
22 #define DMA_ADDR        0x04UL  /* rw  DMA transfer address register  0x04   */
23 #define DMA_COUNT       0x08UL  /* rw  DMA transfer count register    0x08   */
24 #define DMA_TEST        0x0cUL  /* rw  DMA test/debug register        0x0c   */
25
26 #include <scsi/scsi_host.h>
27
28 #include "esp_scsi.h"
29
30 #define DRV_MODULE_NAME         "sun3x_esp"
31 #define PFX DRV_MODULE_NAME     ": "
32 #define DRV_VERSION             "1.000"
33 #define DRV_MODULE_RELDATE      "Nov 1, 2007"
34
35 /*
36  * m68k always assumes readl/writel operate on little endian
37  * mmio space; this is wrong at least for Sun3x, so we
38  * need to workaround this until a proper way is found
39  */
40 #if 0
41 #define dma_read32(REG) \
42         readl(esp->dma_regs + (REG))
43 #define dma_write32(VAL, REG) \
44         writel((VAL), esp->dma_regs + (REG))
45 #else
46 #define dma_read32(REG) \
47         *(volatile u32 *)(esp->dma_regs + (REG))
48 #define dma_write32(VAL, REG) \
49         do { *(volatile u32 *)(esp->dma_regs + (REG)) = (VAL); } while (0)
50 #endif
51
52 static void sun3x_esp_write8(struct esp *esp, u8 val, unsigned long reg)
53 {
54         writeb(val, esp->regs + (reg * 4UL));
55 }
56
57 static u8 sun3x_esp_read8(struct esp *esp, unsigned long reg)
58 {
59         return readb(esp->regs + (reg * 4UL));
60 }
61
62 static dma_addr_t sun3x_esp_map_single(struct esp *esp, void *buf,
63                                       size_t sz, int dir)
64 {
65         return dma_map_single(esp->dev, buf, sz, dir);
66 }
67
68 static int sun3x_esp_map_sg(struct esp *esp, struct scatterlist *sg,
69                                   int num_sg, int dir)
70 {
71         return dma_map_sg(esp->dev, sg, num_sg, dir);
72 }
73
74 static void sun3x_esp_unmap_single(struct esp *esp, dma_addr_t addr,
75                                   size_t sz, int dir)
76 {
77         dma_unmap_single(esp->dev, addr, sz, dir);
78 }
79
80 static void sun3x_esp_unmap_sg(struct esp *esp, struct scatterlist *sg,
81                               int num_sg, int dir)
82 {
83         dma_unmap_sg(esp->dev, sg, num_sg, dir);
84 }
85
86 static int sun3x_esp_irq_pending(struct esp *esp)
87 {
88         if (dma_read32(DMA_CSR) & (DMA_HNDL_INTR | DMA_HNDL_ERROR))
89                 return 1;
90         return 0;
91 }
92
93 static void sun3x_esp_reset_dma(struct esp *esp)
94 {
95         u32 val;
96
97         val = dma_read32(DMA_CSR);
98         dma_write32(val | DMA_RST_SCSI, DMA_CSR);
99         dma_write32(val & ~DMA_RST_SCSI, DMA_CSR);
100
101         /* Enable interrupts.  */
102         val = dma_read32(DMA_CSR);
103         dma_write32(val | DMA_INT_ENAB, DMA_CSR);
104 }
105
106 static void sun3x_esp_dma_drain(struct esp *esp)
107 {
108         u32 csr;
109         int lim;
110
111         csr = dma_read32(DMA_CSR);
112         if (!(csr & DMA_FIFO_ISDRAIN))
113                 return;
114
115         dma_write32(csr | DMA_FIFO_STDRAIN, DMA_CSR);
116
117         lim = 1000;
118         while (dma_read32(DMA_CSR) & DMA_FIFO_ISDRAIN) {
119                 if (--lim == 0) {
120                         printk(KERN_ALERT PFX "esp%d: DMA will not drain!\n",
121                                esp->host->unique_id);
122                         break;
123                 }
124                 udelay(1);
125         }
126 }
127
128 static void sun3x_esp_dma_invalidate(struct esp *esp)
129 {
130         u32 val;
131         int lim;
132
133         lim = 1000;
134         while ((val = dma_read32(DMA_CSR)) & DMA_PEND_READ) {
135                 if (--lim == 0) {
136                         printk(KERN_ALERT PFX "esp%d: DMA will not "
137                                "invalidate!\n", esp->host->unique_id);
138                         break;
139                 }
140                 udelay(1);
141         }
142
143         val &= ~(DMA_ENABLE | DMA_ST_WRITE | DMA_BCNT_ENAB);
144         val |= DMA_FIFO_INV;
145         dma_write32(val, DMA_CSR);
146         val &= ~DMA_FIFO_INV;
147         dma_write32(val, DMA_CSR);
148 }
149
150 static void sun3x_esp_send_dma_cmd(struct esp *esp, u32 addr, u32 esp_count,
151                                   u32 dma_count, int write, u8 cmd)
152 {
153         u32 csr;
154
155         BUG_ON(!(cmd & ESP_CMD_DMA));
156
157         sun3x_esp_write8(esp, (esp_count >> 0) & 0xff, ESP_TCLOW);
158         sun3x_esp_write8(esp, (esp_count >> 8) & 0xff, ESP_TCMED);
159         csr = dma_read32(DMA_CSR);
160         csr |= DMA_ENABLE;
161         if (write)
162                 csr |= DMA_ST_WRITE;
163         else
164                 csr &= ~DMA_ST_WRITE;
165         dma_write32(csr, DMA_CSR);
166         dma_write32(addr, DMA_ADDR);
167
168         scsi_esp_cmd(esp, cmd);
169 }
170
171 static int sun3x_esp_dma_error(struct esp *esp)
172 {
173         u32 csr = dma_read32(DMA_CSR);
174
175         if (csr & DMA_HNDL_ERROR)
176                 return 1;
177
178         return 0;
179 }
180
181 static const struct esp_driver_ops sun3x_esp_ops = {
182         .esp_write8     =       sun3x_esp_write8,
183         .esp_read8      =       sun3x_esp_read8,
184         .map_single     =       sun3x_esp_map_single,
185         .map_sg         =       sun3x_esp_map_sg,
186         .unmap_single   =       sun3x_esp_unmap_single,
187         .unmap_sg       =       sun3x_esp_unmap_sg,
188         .irq_pending    =       sun3x_esp_irq_pending,
189         .reset_dma      =       sun3x_esp_reset_dma,
190         .dma_drain      =       sun3x_esp_dma_drain,
191         .dma_invalidate =       sun3x_esp_dma_invalidate,
192         .send_dma_cmd   =       sun3x_esp_send_dma_cmd,
193         .dma_error      =       sun3x_esp_dma_error,
194 };
195
196 static int __devinit esp_sun3x_probe(struct platform_device *dev)
197 {
198         struct scsi_host_template *tpnt = &scsi_esp_template;
199         struct Scsi_Host *host;
200         struct esp *esp;
201         struct resource *res;
202         int err = -ENOMEM;
203
204         host = scsi_host_alloc(tpnt, sizeof(struct esp));
205         if (!host)
206                 goto fail;
207
208         host->max_id = 8;
209         esp = shost_priv(host);
210
211         esp->host = host;
212         esp->dev = dev;
213         esp->ops = &sun3x_esp_ops;
214
215         res = platform_get_resource(dev, IORESOURCE_MEM, 0);
216         if (!res || !res->start)
217                 goto fail_unlink;
218
219         esp->regs = ioremap_nocache(res->start, 0x20);
220         if (!esp->regs)
221                 goto fail_unmap_regs;
222
223         res = platform_get_resource(dev, IORESOURCE_MEM, 1);
224         if (!res || !res->start)
225                 goto fail_unmap_regs;
226
227         esp->dma_regs = ioremap_nocache(res->start, 0x10);
228
229         esp->command_block = dma_alloc_coherent(esp->dev, 16,
230                                                 &esp->command_block_dma,
231                                                 GFP_KERNEL);
232         if (!esp->command_block)
233                 goto fail_unmap_regs_dma;
234
235         host->irq = platform_get_irq(dev, 0);
236         err = request_irq(host->irq, scsi_esp_intr, IRQF_SHARED,
237                           "SUN3X ESP", esp);
238         if (err < 0)
239                 goto fail_unmap_command_block;
240
241         esp->scsi_id = 7;
242         esp->host->this_id = esp->scsi_id;
243         esp->scsi_id_mask = (1 << esp->scsi_id);
244         esp->cfreq = 20000000;
245
246         dev_set_drvdata(&dev->dev, esp);
247
248         err = scsi_esp_register(esp, &dev->dev);
249         if (err)
250                 goto fail_free_irq;
251
252         return 0;
253
254 fail_free_irq:
255         free_irq(host->irq, esp);
256 fail_unmap_command_block:
257         dma_free_coherent(esp->dev, 16,
258                           esp->command_block,
259                           esp->command_block_dma);
260 fail_unmap_regs_dma:
261         iounmap(esp->dma_regs);
262 fail_unmap_regs:
263         iounmap(esp->regs);
264 fail_unlink:
265         scsi_host_put(host);
266 fail:
267         return err;
268 }
269
270 static int __devexit esp_sun3x_remove(struct platform_device *dev)
271 {
272         struct esp *esp = dev_get_drvdata(&dev->dev);
273         unsigned int irq = esp->host->irq;
274         u32 val;
275
276         scsi_esp_unregister(esp);
277
278         /* Disable interrupts.  */
279         val = dma_read32(DMA_CSR);
280         dma_write32(val & ~DMA_INT_ENAB, DMA_CSR);
281
282         free_irq(irq, esp);
283         dma_free_coherent(esp->dev, 16,
284                           esp->command_block,
285                           esp->command_block_dma);
286
287         scsi_host_put(esp->host);
288
289         return 0;
290 }
291
292 static struct platform_driver esp_sun3x_driver = {
293         .probe          = esp_sun3x_probe,
294         .remove         = __devexit_p(esp_sun3x_remove),
295         .driver = {
296                 .name   = "sun3x_esp",
297                 .owner  = THIS_MODULE,
298         },
299 };
300
301 static int __init sun3x_esp_init(void)
302 {
303         return platform_driver_register(&esp_sun3x_driver);
304 }
305
306 static void __exit sun3x_esp_exit(void)
307 {
308         platform_driver_unregister(&esp_sun3x_driver);
309 }
310
311 MODULE_DESCRIPTION("Sun3x ESP SCSI driver");
312 MODULE_AUTHOR("Thomas Bogendoerfer (tsbogend@alpha.franken.de)");
313 MODULE_LICENSE("GPL");
314 MODULE_VERSION(DRV_VERSION);
315
316 module_init(sun3x_esp_init);
317 module_exit(sun3x_esp_exit);
318 MODULE_ALIAS("platform:sun3x_esp");