blob: bfddc73d0a200b358b80761891106e5237b22799 [file] [log] [blame]
Krzysztof Halasa82a96f52008-01-01 21:55:23 +01001/*
2 * Intel IXP4xx Queue Manager driver for Linux
3 *
4 * Copyright (C) 2007 Krzysztof Halasa <khc@pm.waw.pl>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of version 2 of the GNU General Public License
8 * as published by the Free Software Foundation.
9 */
10
11#include <linux/ioport.h>
12#include <linux/interrupt.h>
13#include <linux/kernel.h>
14#include <linux/module.h>
Russell Kinga09e64f2008-08-05 16:14:15 +010015#include <mach/qmgr.h>
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010016
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010017struct qmgr_regs __iomem *qmgr_regs;
18static struct resource *mem_res;
19static spinlock_t qmgr_lock;
20static u32 used_sram_bitmap[4]; /* 128 16-dword pages */
21static void (*irq_handlers[HALF_QUEUES])(void *pdev);
22static void *irq_pdevs[HALF_QUEUES];
23
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +010024#if DEBUG_QMGR
25char qmgr_queue_descs[QUEUES][32];
26#endif
27
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010028void qmgr_set_irq(unsigned int queue, int src,
29 void (*handler)(void *pdev), void *pdev)
30{
31 u32 __iomem *reg = &qmgr_regs->irqsrc[queue / 8]; /* 8 queues / u32 */
32 int bit = (queue % 8) * 4; /* 3 bits + 1 reserved bit per queue */
33 unsigned long flags;
34
35 src &= 7;
36 spin_lock_irqsave(&qmgr_lock, flags);
37 __raw_writel((__raw_readl(reg) & ~(7 << bit)) | (src << bit), reg);
38 irq_handlers[queue] = handler;
39 irq_pdevs[queue] = pdev;
40 spin_unlock_irqrestore(&qmgr_lock, flags);
41}
42
43
44static irqreturn_t qmgr_irq1(int irq, void *pdev)
45{
46 int i;
47 u32 val = __raw_readl(&qmgr_regs->irqstat[0]);
48 __raw_writel(val, &qmgr_regs->irqstat[0]); /* ACK */
49
50 for (i = 0; i < HALF_QUEUES; i++)
51 if (val & (1 << i))
52 irq_handlers[i](irq_pdevs[i]);
53
54 return val ? IRQ_HANDLED : 0;
55}
56
57
58void qmgr_enable_irq(unsigned int queue)
59{
60 unsigned long flags;
61
62 spin_lock_irqsave(&qmgr_lock, flags);
63 __raw_writel(__raw_readl(&qmgr_regs->irqen[0]) | (1 << queue),
64 &qmgr_regs->irqen[0]);
65 spin_unlock_irqrestore(&qmgr_lock, flags);
66}
67
68void qmgr_disable_irq(unsigned int queue)
69{
70 unsigned long flags;
71
72 spin_lock_irqsave(&qmgr_lock, flags);
73 __raw_writel(__raw_readl(&qmgr_regs->irqen[0]) & ~(1 << queue),
74 &qmgr_regs->irqen[0]);
Krzysztof Hałasaae2754a2008-05-09 02:14:09 +020075 __raw_writel(1 << queue, &qmgr_regs->irqstat[0]); /* clear */
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010076 spin_unlock_irqrestore(&qmgr_lock, flags);
77}
78
79static inline void shift_mask(u32 *mask)
80{
81 mask[3] = mask[3] << 1 | mask[2] >> 31;
82 mask[2] = mask[2] << 1 | mask[1] >> 31;
83 mask[1] = mask[1] << 1 | mask[0] >> 31;
84 mask[0] <<= 1;
85}
86
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +010087#if DEBUG_QMGR
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010088int qmgr_request_queue(unsigned int queue, unsigned int len /* dwords */,
89 unsigned int nearly_empty_watermark,
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +010090 unsigned int nearly_full_watermark,
91 const char *desc_format, const char* name)
92#else
93int __qmgr_request_queue(unsigned int queue, unsigned int len /* dwords */,
94 unsigned int nearly_empty_watermark,
95 unsigned int nearly_full_watermark)
96#endif
Krzysztof Halasa82a96f52008-01-01 21:55:23 +010097{
98 u32 cfg, addr = 0, mask[4]; /* in 16-dwords */
99 int err;
100
101 if (queue >= HALF_QUEUES)
102 return -ERANGE;
103
104 if ((nearly_empty_watermark | nearly_full_watermark) & ~7)
105 return -EINVAL;
106
107 switch (len) {
108 case 16:
109 cfg = 0 << 24;
110 mask[0] = 0x1;
111 break;
112 case 32:
113 cfg = 1 << 24;
114 mask[0] = 0x3;
115 break;
116 case 64:
117 cfg = 2 << 24;
118 mask[0] = 0xF;
119 break;
120 case 128:
121 cfg = 3 << 24;
122 mask[0] = 0xFF;
123 break;
124 default:
125 return -EINVAL;
126 }
127
128 cfg |= nearly_empty_watermark << 26;
129 cfg |= nearly_full_watermark << 29;
130 len /= 16; /* in 16-dwords: 1, 2, 4 or 8 */
131 mask[1] = mask[2] = mask[3] = 0;
132
133 if (!try_module_get(THIS_MODULE))
134 return -ENODEV;
135
136 spin_lock_irq(&qmgr_lock);
137 if (__raw_readl(&qmgr_regs->sram[queue])) {
138 err = -EBUSY;
139 goto err;
140 }
141
142 while (1) {
143 if (!(used_sram_bitmap[0] & mask[0]) &&
144 !(used_sram_bitmap[1] & mask[1]) &&
145 !(used_sram_bitmap[2] & mask[2]) &&
146 !(used_sram_bitmap[3] & mask[3]))
147 break; /* found free space */
148
149 addr++;
150 shift_mask(mask);
151 if (addr + len > ARRAY_SIZE(qmgr_regs->sram)) {
152 printk(KERN_ERR "qmgr: no free SRAM space for"
153 " queue %i\n", queue);
154 err = -ENOMEM;
155 goto err;
156 }
157 }
158
159 used_sram_bitmap[0] |= mask[0];
160 used_sram_bitmap[1] |= mask[1];
161 used_sram_bitmap[2] |= mask[2];
162 used_sram_bitmap[3] |= mask[3];
163 __raw_writel(cfg | (addr << 14), &qmgr_regs->sram[queue]);
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100164#if DEBUG_QMGR
165 snprintf(qmgr_queue_descs[queue], sizeof(qmgr_queue_descs[0]),
166 desc_format, name);
167 printk(KERN_DEBUG "qmgr: requested queue %s(%i) addr = 0x%02X\n",
168 qmgr_queue_descs[queue], queue, addr);
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100169#endif
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100170 spin_unlock_irq(&qmgr_lock);
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100171 return 0;
172
173err:
174 spin_unlock_irq(&qmgr_lock);
175 module_put(THIS_MODULE);
176 return err;
177}
178
179void qmgr_release_queue(unsigned int queue)
180{
181 u32 cfg, addr, mask[4];
182
183 BUG_ON(queue >= HALF_QUEUES); /* not in valid range */
184
185 spin_lock_irq(&qmgr_lock);
186 cfg = __raw_readl(&qmgr_regs->sram[queue]);
187 addr = (cfg >> 14) & 0xFF;
188
189 BUG_ON(!addr); /* not requested */
190
191 switch ((cfg >> 24) & 3) {
192 case 0: mask[0] = 0x1; break;
193 case 1: mask[0] = 0x3; break;
194 case 2: mask[0] = 0xF; break;
195 case 3: mask[0] = 0xFF; break;
196 }
197
Krzysztof Halasadac2f832008-04-20 19:06:39 +0200198 mask[1] = mask[2] = mask[3] = 0;
199
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100200 while (addr--)
201 shift_mask(mask);
202
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100203#if DEBUG_QMGR
204 printk(KERN_DEBUG "qmgr: releasing queue %s(%i)\n",
205 qmgr_queue_descs[queue], queue);
206 qmgr_queue_descs[queue][0] = '\x0';
207#endif
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100208 __raw_writel(0, &qmgr_regs->sram[queue]);
209
210 used_sram_bitmap[0] &= ~mask[0];
211 used_sram_bitmap[1] &= ~mask[1];
212 used_sram_bitmap[2] &= ~mask[2];
213 used_sram_bitmap[3] &= ~mask[3];
214 irq_handlers[queue] = NULL; /* catch IRQ bugs */
215 spin_unlock_irq(&qmgr_lock);
216
217 module_put(THIS_MODULE);
Krzysztof Hałasa3edcfb22008-05-08 23:18:31 +0200218
219 while ((addr = qmgr_get_entry(queue)))
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100220 printk(KERN_ERR "qmgr: released queue %i not empty: 0x%08X\n",
Krzysztof Hałasa3edcfb22008-05-08 23:18:31 +0200221 queue, addr);
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100222}
223
224static int qmgr_init(void)
225{
226 int i, err;
227 mem_res = request_mem_region(IXP4XX_QMGR_BASE_PHYS,
228 IXP4XX_QMGR_REGION_SIZE,
229 "IXP4xx Queue Manager");
230 if (mem_res == NULL)
231 return -EBUSY;
232
233 qmgr_regs = ioremap(IXP4XX_QMGR_BASE_PHYS, IXP4XX_QMGR_REGION_SIZE);
234 if (qmgr_regs == NULL) {
235 err = -ENOMEM;
236 goto error_map;
237 }
238
239 /* reset qmgr registers */
240 for (i = 0; i < 4; i++) {
241 __raw_writel(0x33333333, &qmgr_regs->stat1[i]);
242 __raw_writel(0, &qmgr_regs->irqsrc[i]);
243 }
244 for (i = 0; i < 2; i++) {
245 __raw_writel(0, &qmgr_regs->stat2[i]);
246 __raw_writel(0xFFFFFFFF, &qmgr_regs->irqstat[i]); /* clear */
247 __raw_writel(0, &qmgr_regs->irqen[i]);
248 }
249
250 for (i = 0; i < QUEUES; i++)
251 __raw_writel(0, &qmgr_regs->sram[i]);
252
253 err = request_irq(IRQ_IXP4XX_QM1, qmgr_irq1, 0,
254 "IXP4xx Queue Manager", NULL);
255 if (err) {
256 printk(KERN_ERR "qmgr: failed to request IRQ%i\n",
257 IRQ_IXP4XX_QM1);
258 goto error_irq;
259 }
260
261 used_sram_bitmap[0] = 0xF; /* 4 first pages reserved for config */
262 spin_lock_init(&qmgr_lock);
263
264 printk(KERN_INFO "IXP4xx Queue Manager initialized.\n");
265 return 0;
266
267error_irq:
268 iounmap(qmgr_regs);
269error_map:
270 release_mem_region(IXP4XX_QMGR_BASE_PHYS, IXP4XX_QMGR_REGION_SIZE);
271 return err;
272}
273
274static void qmgr_remove(void)
275{
276 free_irq(IRQ_IXP4XX_QM1, NULL);
277 synchronize_irq(IRQ_IXP4XX_QM1);
278 iounmap(qmgr_regs);
279 release_mem_region(IXP4XX_QMGR_BASE_PHYS, IXP4XX_QMGR_REGION_SIZE);
280}
281
282module_init(qmgr_init);
283module_exit(qmgr_remove);
284
285MODULE_LICENSE("GPL v2");
286MODULE_AUTHOR("Krzysztof Halasa");
287
288EXPORT_SYMBOL(qmgr_regs);
289EXPORT_SYMBOL(qmgr_set_irq);
290EXPORT_SYMBOL(qmgr_enable_irq);
291EXPORT_SYMBOL(qmgr_disable_irq);
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100292#if DEBUG_QMGR
293EXPORT_SYMBOL(qmgr_queue_descs);
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100294EXPORT_SYMBOL(qmgr_request_queue);
Krzysztof Hałasae6da96a2008-12-22 00:26:38 +0100295#else
296EXPORT_SYMBOL(__qmgr_request_queue);
297#endif
Krzysztof Halasa82a96f52008-01-01 21:55:23 +0100298EXPORT_SYMBOL(qmgr_release_queue);