/alps/pcitool

To get this branch, use:
bzr branch http://darksoft.org/webbzr/alps/pcitool

« back to all changes in this revision

Viewing changes to driver/int.c

  • Committer: Suren A. Chilingaryan
  • Date: 2011-02-13 02:07:11 UTC
  • Revision ID: csa@dside.dyndns.org-20110213020711-y9bjh3n4ke6p4t4n
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 *
 
3
 * @file int.c
 
4
 * @author Guillermo Marcus
 
5
 * @date 2009-04-05
 
6
 * @brief Contains the interrupt handler.
 
7
 *
 
8
 */
 
9
 
 
10
/*
 
11
 * Change History:
 
12
 * 
 
13
 * $Log: not supported by cvs2svn $
 
14
 * Revision 1.7  2008-01-11 10:18:28  marcus
 
15
 * Modified interrupt mechanism. Added atomic functions and queues, to address race conditions. Removed unused interrupt code.
 
16
 *
 
17
 * Revision 1.6  2007-11-04 20:58:22  marcus
 
18
 * Added interrupt generator acknowledge.
 
19
 * Fixed wrong operator.
 
20
 *
 
21
 * Revision 1.5  2007-10-31 15:42:21  marcus
 
22
 * Added IG ack for testing, may be removed later.
 
23
 *
 
24
 * Revision 1.4  2007-07-17 13:15:56  marcus
 
25
 * Removed Tasklets.
 
26
 * Using newest map for the ABB interrupts.
 
27
 *
 
28
 * Revision 1.3  2007-07-05 15:30:30  marcus
 
29
 * Added support for both register maps of the ABB.
 
30
 *
 
31
 * Revision 1.2  2007-05-29 07:50:18  marcus
 
32
 * Split code into 2 files. May get merged in the future again....
 
33
 *
 
34
 * Revision 1.1  2007/03/01 16:57:43  marcus
 
35
 * Divided driver file to ease the interrupt hooks for the user of the driver.
 
36
 * Modified Makefile accordingly.
 
37
 *
 
38
 */
 
39
 
 
40
#include <linux/version.h>
 
41
#include <linux/string.h>
 
42
#include <linux/types.h>
 
43
#include <linux/list.h>
 
44
#include <linux/interrupt.h>
 
45
#include <linux/pci.h>
 
46
#include <linux/cdev.h>
 
47
#include <linux/wait.h>
 
48
#include <linux/sched.h>
 
49
#include <stdbool.h>
 
50
 
 
51
#include "config.h"
 
52
 
 
53
#include "compat.h"
 
54
 
 
55
#include "pciDriver.h"
 
56
 
 
57
#include "common.h"
 
58
 
 
59
#include "int.h"
 
60
 
 
61
/*
 
62
 * The ID between IRQ_SOURCE in irq_outstanding and the actual source is arbitrary.
 
63
 * Therefore, be careful when communicating with multiple implementations. 
 
64
 */
 
65
 
 
66
/* IRQ_SOURCES */
 
67
#define ABB_IRQ_CH0             0
 
68
#define ABB_IRQ_CH1             1
 
69
#define ABB_IRQ_IG              2
 
70
 
 
71
/* See ABB user’s guide, register definitions (3.1) */
 
72
#define ABB_INT_ENABLE          (0x0010 >> 2)
 
73
#define ABB_INT_STAT            (0x0008 >> 2)
 
74
 
 
75
#define ABB_INT_CH1_TIMEOUT     (1 << 4)
 
76
#define ABB_INT_CH0_TIMEOUT     (1 << 5)
 
77
#define ABB_INT_IG              (1 << 2)
 
78
#define ABB_INT_CH0             (1 << 1) /* downstream */
 
79
#define ABB_INT_CH1             (1)     /* upstream */
 
80
 
 
81
#define ABB_CH0_CTRL            (108 >> 2)
 
82
#define ABB_CH1_CTRL            (72 >> 2)
 
83
#define ABB_CH_RESET            (0x0201000A)
 
84
#define ABB_IG_CTRL             (0x0080 >> 2)
 
85
#define ABB_IG_ACK              (0x00F0)
 
86
 
 
87
/**
 
88
 *
 
89
 * If IRQ-handling is enabled, this function will be called from pcidriver_probe
 
90
 * to initialize the IRQ handling (maps the BARs)
 
91
 *
 
92
 */
 
93
int pcidriver_probe_irq(pcidriver_privdata_t *privdata)
 
94
{
 
95
        unsigned char int_pin, int_line;
 
96
        unsigned long bar_addr, bar_len, bar_flags;
 
97
        int i;
 
98
        int err;
 
99
 
 
100
        for (i = 0; i < 6; i++)
 
101
                privdata->bars_kmapped[i] = NULL;
 
102
 
 
103
        for (i = 0; i < 6; i++) {
 
104
                bar_addr = pci_resource_start(privdata->pdev, i);
 
105
                bar_len = pci_resource_len(privdata->pdev, i);
 
106
                bar_flags = pci_resource_flags(privdata->pdev, i);
 
107
 
 
108
                /* check if it is a valid BAR, skip if not */
 
109
                if ((bar_addr == 0) || (bar_len == 0))
 
110
                        continue;
 
111
 
 
112
                /* Skip IO regions (map only mem regions) */
 
113
                if (bar_flags & IORESOURCE_IO)
 
114
                        continue;
 
115
 
 
116
                /* Check if the region is available */
 
117
                if ((err = pci_request_region(privdata->pdev, i, NULL)) != 0) {
 
118
                        mod_info( "Failed to request BAR memory region.\n" );
 
119
                        return err;
 
120
                }
 
121
 
 
122
                /* Map it into kernel space. */
 
123
                /* For x86 this is just a dereference of the pointer, but that is
 
124
                 * not portable. So we need to do the portable way. Thanks Joern!
 
125
                 */
 
126
 
 
127
                /* respect the cacheable-bility of the region */
 
128
                if (bar_flags & IORESOURCE_PREFETCH)
 
129
                        privdata->bars_kmapped[i] = ioremap(bar_addr, bar_len);
 
130
                else
 
131
                        privdata->bars_kmapped[i] = ioremap_nocache(bar_addr, bar_len);
 
132
 
 
133
                /* check for error */
 
134
                if (privdata->bars_kmapped[i] == NULL) {
 
135
                        mod_info( "Failed to remap BAR%d into kernel space.\n", i );
 
136
                        return -EIO;
 
137
                }
 
138
        }
 
139
 
 
140
        /* Initialize the interrupt handler for this device */
 
141
        /* Initialize the wait queues */
 
142
        for (i = 0; i < PCIDRIVER_INT_MAXSOURCES; i++) {
 
143
                init_waitqueue_head(&(privdata->irq_queues[i]));
 
144
                atomic_set(&(privdata->irq_outstanding[i]), 0);
 
145
        }
 
146
 
 
147
        /* Initialize the irq config */
 
148
        if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &int_pin)) != 0) {
 
149
                /* continue without interrupts */
 
150
                int_pin = 0;
 
151
                mod_info("Error getting the interrupt pin. Disabling interrupts for this device\n");
 
152
        }
 
153
 
 
154
        /* Disable interrupts and activate them if everything can be set up properly */
 
155
        privdata->irq_enabled = 0;
 
156
 
 
157
        if (int_pin == 0)
 
158
                return 0;
 
159
 
 
160
        if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &int_line)) != 0) {
 
161
                mod_info("Error getting the interrupt line. Disabling interrupts for this device\n");
 
162
                return 0;
 
163
        }
 
164
 
 
165
        /* register interrupt handler */
 
166
        if ((err = request_irq(privdata->pdev->irq, pcidriver_irq_handler, MODNAME, privdata)) != 0) {
 
167
                mod_info("Error registering the interrupt handler. Disabling interrupts for this device\n");
 
168
                return 0;
 
169
        }
 
170
 
 
171
        privdata->irq_enabled = 1;
 
172
        mod_info("Registered Interrupt Handler at pin %i, line %i, IRQ %i\n", int_pin, int_line, privdata->pdev->irq );
 
173
 
 
174
        return 0;
 
175
}
 
176
 
 
177
/**
 
178
 *
 
179
 * Frees/cleans up the data structures, called from pcidriver_remove()
 
180
 *
 
181
 */
 
182
void pcidriver_remove_irq(pcidriver_privdata_t *privdata)
 
183
{
 
184
        /* Release the IRQ handler */
 
185
        if (privdata->irq_enabled != 0)
 
186
                free_irq(privdata->pdev->irq, privdata);
 
187
 
 
188
        pcidriver_irq_unmap_bars(privdata);
 
189
}
 
190
 
 
191
/**
 
192
 *
 
193
 * Unmaps the BARs and releases them
 
194
 *
 
195
 */
 
196
void pcidriver_irq_unmap_bars(pcidriver_privdata_t *privdata)
 
197
{
 
198
        int i;
 
199
 
 
200
        for (i = 0; i < 6; i++) {
 
201
                if (privdata->bars_kmapped[i] == NULL)
 
202
                        continue;
 
203
 
 
204
                iounmap((void*)privdata->bars_kmapped[i]);
 
205
                pci_release_region(privdata->pdev, i);
 
206
        }
 
207
}
 
208
 
 
209
/**
 
210
 *
 
211
 * Acknowledge the interrupt by ACKing the interrupt generator.
 
212
 *
 
213
 * @returns true if the channel was acknowledged and the interrupt handler is done
 
214
 *
 
215
 */
 
216
static bool check_acknowlegde_channel(pcidriver_privdata_t *privdata, int interrupt,
 
217
                                      int channel, volatile unsigned int *bar)
 
218
{
 
219
        if (!(bar[ABB_INT_STAT] & interrupt))
 
220
                return false;
 
221
 
 
222
        bar[ABB_INT_ENABLE] &= !interrupt;
 
223
        if (interrupt == ABB_INT_IG)
 
224
                bar[ABB_IG_CTRL] = ABB_IG_ACK;
 
225
 
 
226
        /* Wake up the waiting loop in ioctl.c:ioctl_wait_interrupt() */
 
227
        atomic_inc(&(privdata->irq_outstanding[channel]));
 
228
        wake_up_interruptible(&(privdata->irq_queues[channel]));
 
229
        return true;
 
230
}
 
231
 
 
232
/**
 
233
 *
 
234
 * Acknowledges the receival of an interrupt to the card.
 
235
 *
 
236
 * @returns true if the card was acknowledget
 
237
 * @returns false if the interrupt was not for one of our cards
 
238
 *
 
239
 * @see check_acknowlegde_channel
 
240
 *
 
241
 */
 
242
static bool pcidriver_irq_acknowledge(pcidriver_privdata_t *privdata)
 
243
{
 
244
        volatile unsigned int *bar;
 
245
 
 
246
        /* TODO: add subvendor / subsystem ids */
 
247
        /* FIXME: guillermo: which ones? all? */
 
248
 
 
249
        /* Test if we have to handle this interrupt */
 
250
        if ((privdata->pdev->vendor != PCIEABB_VENDOR_ID) ||
 
251
            (privdata->pdev->device != PCIEABB_DEVICE_ID))
 
252
                return false;
 
253
 
 
254
        /* Acknowledge the device */
 
255
        /* this is for ABB / wenxue DMA engine */
 
256
        bar = privdata->bars_kmapped[0];
 
257
 
 
258
        mod_info_dbg("interrupt registers. ISR: %x, IER: %x\n", bar[ABB_INT_STAT], bar[ABB_INT_ENABLE]);
 
259
 
 
260
        if (check_acknowlegde_channel(privdata, ABB_INT_CH0, ABB_IRQ_CH0, bar))
 
261
                return true;
 
262
 
 
263
        if (check_acknowlegde_channel(privdata, ABB_INT_CH1, ABB_IRQ_CH1, bar))
 
264
                return true;
 
265
 
 
266
        if (check_acknowlegde_channel(privdata, ABB_INT_IG, ABB_IRQ_IG, bar))
 
267
                return true;
 
268
 
 
269
        if (check_acknowlegde_channel(privdata, ABB_INT_CH0_TIMEOUT, ABB_IRQ_CH0, bar))
 
270
                return true;
 
271
 
 
272
        if (check_acknowlegde_channel(privdata, ABB_INT_CH1_TIMEOUT, ABB_IRQ_CH1, bar))
 
273
                return true;
 
274
 
 
275
        mod_info_dbg("err: interrupt registers. ISR: %x, IER: %x\n", bar[ ABB_INT_STAT ], bar[ ABB_INT_ENABLE ] );
 
276
 
 
277
        return false;
 
278
}
 
279
 
 
280
/**
 
281
 *
 
282
 * Handles IRQs. At the moment, this acknowledges the card that this IRQ
 
283
 * was received and then increases the driver's IRQ counter.
 
284
 *
 
285
 * @see pcidriver_irq_acknowledge
 
286
 *
 
287
 */
 
288
IRQ_HANDLER_FUNC(pcidriver_irq_handler)
 
289
{
 
290
        pcidriver_privdata_t *privdata = (pcidriver_privdata_t *)dev_id;
 
291
 
 
292
        if (!pcidriver_irq_acknowledge(privdata))
 
293
                return IRQ_NONE;
 
294
 
 
295
        privdata->irq_count++;
 
296
        return IRQ_HANDLED;
 
297
}