Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
9c26df9b WBG |
2 | /* |
3 | * GPIO driver for the WinSystems WS16C48 | |
4 | * Copyright (C) 2016 William Breathitt Gray | |
9c26df9b | 5 | */ |
a8ff510d | 6 | #include <linux/bitmap.h> |
9c26df9b WBG |
7 | #include <linux/bitops.h> |
8 | #include <linux/device.h> | |
9 | #include <linux/errno.h> | |
10 | #include <linux/gpio/driver.h> | |
11 | #include <linux/io.h> | |
12 | #include <linux/ioport.h> | |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/irqdesc.h> | |
cc736607 | 15 | #include <linux/isa.h> |
9c26df9b WBG |
16 | #include <linux/kernel.h> |
17 | #include <linux/module.h> | |
18 | #include <linux/moduleparam.h> | |
9c26df9b WBG |
19 | #include <linux/spinlock.h> |
20 | ||
cc736607 WBG |
21 | #define WS16C48_EXTENT 16 |
22 | #define MAX_NUM_WS16C48 max_num_isa_dev(WS16C48_EXTENT) | |
23 | ||
24 | static unsigned int base[MAX_NUM_WS16C48]; | |
25 | static unsigned int num_ws16c48; | |
d759f906 | 26 | module_param_hw_array(base, uint, ioport, &num_ws16c48, 0); |
cc736607 WBG |
27 | MODULE_PARM_DESC(base, "WinSystems WS16C48 base addresses"); |
28 | ||
29 | static unsigned int irq[MAX_NUM_WS16C48]; | |
d759f906 | 30 | module_param_hw_array(irq, uint, irq, NULL, 0); |
cc736607 | 31 | MODULE_PARM_DESC(irq, "WinSystems WS16C48 interrupt line numbers"); |
9c26df9b WBG |
32 | |
33 | /** | |
34 | * struct ws16c48_gpio - GPIO device private data structure | |
35 | * @chip: instance of the gpio_chip | |
36 | * @io_state: bit I/O state (whether bit is set to input or output) | |
37 | * @out_state: output bits state | |
38 | * @lock: synchronization lock to prevent I/O race conditions | |
39 | * @irq_mask: I/O bits affected by interrupts | |
40 | * @flow_mask: IRQ flow type mask for the respective I/O bits | |
41 | * @base: base port address of the GPIO device | |
9c26df9b WBG |
42 | */ |
43 | struct ws16c48_gpio { | |
44 | struct gpio_chip chip; | |
45 | unsigned char io_state[6]; | |
46 | unsigned char out_state[6]; | |
a0a584f0 | 47 | raw_spinlock_t lock; |
9c26df9b WBG |
48 | unsigned long irq_mask; |
49 | unsigned long flow_mask; | |
50 | unsigned base; | |
9c26df9b WBG |
51 | }; |
52 | ||
53 | static int ws16c48_gpio_get_direction(struct gpio_chip *chip, unsigned offset) | |
54 | { | |
55 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
56 | const unsigned port = offset / 8; | |
57 | const unsigned mask = BIT(offset % 8); | |
58 | ||
59 | return !!(ws16c48gpio->io_state[port] & mask); | |
60 | } | |
61 | ||
62 | static int ws16c48_gpio_direction_input(struct gpio_chip *chip, unsigned offset) | |
63 | { | |
64 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
65 | const unsigned port = offset / 8; | |
66 | const unsigned mask = BIT(offset % 8); | |
67 | unsigned long flags; | |
68 | ||
a0a584f0 | 69 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
70 | |
71 | ws16c48gpio->io_state[port] |= mask; | |
72 | ws16c48gpio->out_state[port] &= ~mask; | |
73 | outb(ws16c48gpio->out_state[port], ws16c48gpio->base + port); | |
74 | ||
a0a584f0 | 75 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
76 | |
77 | return 0; | |
78 | } | |
79 | ||
80 | static int ws16c48_gpio_direction_output(struct gpio_chip *chip, | |
81 | unsigned offset, int value) | |
82 | { | |
83 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
84 | const unsigned port = offset / 8; | |
85 | const unsigned mask = BIT(offset % 8); | |
86 | unsigned long flags; | |
87 | ||
a0a584f0 | 88 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
89 | |
90 | ws16c48gpio->io_state[port] &= ~mask; | |
91 | if (value) | |
92 | ws16c48gpio->out_state[port] |= mask; | |
93 | else | |
94 | ws16c48gpio->out_state[port] &= ~mask; | |
95 | outb(ws16c48gpio->out_state[port], ws16c48gpio->base + port); | |
96 | ||
a0a584f0 | 97 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
98 | |
99 | return 0; | |
100 | } | |
101 | ||
102 | static int ws16c48_gpio_get(struct gpio_chip *chip, unsigned offset) | |
103 | { | |
104 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
105 | const unsigned port = offset / 8; | |
106 | const unsigned mask = BIT(offset % 8); | |
107 | unsigned long flags; | |
108 | unsigned port_state; | |
109 | ||
a0a584f0 | 110 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
111 | |
112 | /* ensure that GPIO is set for input */ | |
113 | if (!(ws16c48gpio->io_state[port] & mask)) { | |
a0a584f0 | 114 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
115 | return -EINVAL; |
116 | } | |
117 | ||
118 | port_state = inb(ws16c48gpio->base + port); | |
119 | ||
a0a584f0 | 120 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
121 | |
122 | return !!(port_state & mask); | |
123 | } | |
124 | ||
a8ff510d WBG |
125 | static int ws16c48_gpio_get_multiple(struct gpio_chip *chip, |
126 | unsigned long *mask, unsigned long *bits) | |
127 | { | |
128 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
129 | const unsigned int gpio_reg_size = 8; | |
130 | size_t i; | |
131 | const size_t num_ports = chip->ngpio / gpio_reg_size; | |
132 | unsigned int bits_offset; | |
133 | size_t word_index; | |
134 | unsigned int word_offset; | |
135 | unsigned long word_mask; | |
136 | const unsigned long port_mask = GENMASK(gpio_reg_size - 1, 0); | |
137 | unsigned long port_state; | |
138 | ||
139 | /* clear bits array to a clean slate */ | |
140 | bitmap_zero(bits, chip->ngpio); | |
141 | ||
142 | /* get bits are evaluated a gpio port register at a time */ | |
143 | for (i = 0; i < num_ports; i++) { | |
144 | /* gpio offset in bits array */ | |
145 | bits_offset = i * gpio_reg_size; | |
146 | ||
147 | /* word index for bits array */ | |
148 | word_index = BIT_WORD(bits_offset); | |
149 | ||
150 | /* gpio offset within current word of bits array */ | |
151 | word_offset = bits_offset % BITS_PER_LONG; | |
152 | ||
153 | /* mask of get bits for current gpio within current word */ | |
154 | word_mask = mask[word_index] & (port_mask << word_offset); | |
155 | if (!word_mask) { | |
156 | /* no get bits in this port so skip to next one */ | |
157 | continue; | |
158 | } | |
159 | ||
160 | /* read bits from current gpio port */ | |
161 | port_state = inb(ws16c48gpio->base + i); | |
162 | ||
163 | /* store acquired bits at respective bits array offset */ | |
7a702691 | 164 | bits[word_index] |= (port_state << word_offset) & word_mask; |
a8ff510d WBG |
165 | } |
166 | ||
167 | return 0; | |
168 | } | |
169 | ||
9c26df9b WBG |
170 | static void ws16c48_gpio_set(struct gpio_chip *chip, unsigned offset, int value) |
171 | { | |
172 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
173 | const unsigned port = offset / 8; | |
174 | const unsigned mask = BIT(offset % 8); | |
175 | unsigned long flags; | |
176 | ||
a0a584f0 | 177 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
178 | |
179 | /* ensure that GPIO is set for output */ | |
180 | if (ws16c48gpio->io_state[port] & mask) { | |
a0a584f0 | 181 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
182 | return; |
183 | } | |
184 | ||
185 | if (value) | |
186 | ws16c48gpio->out_state[port] |= mask; | |
187 | else | |
188 | ws16c48gpio->out_state[port] &= ~mask; | |
189 | outb(ws16c48gpio->out_state[port], ws16c48gpio->base + port); | |
190 | ||
a0a584f0 | 191 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
192 | } |
193 | ||
99c8ac95 WBG |
194 | static void ws16c48_gpio_set_multiple(struct gpio_chip *chip, |
195 | unsigned long *mask, unsigned long *bits) | |
196 | { | |
197 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
198 | unsigned int i; | |
199 | const unsigned int gpio_reg_size = 8; | |
200 | unsigned int port; | |
201 | unsigned int iomask; | |
202 | unsigned int bitmask; | |
203 | unsigned long flags; | |
204 | ||
205 | /* set bits are evaluated a gpio register size at a time */ | |
206 | for (i = 0; i < chip->ngpio; i += gpio_reg_size) { | |
207 | /* no more set bits in this mask word; skip to the next word */ | |
208 | if (!mask[BIT_WORD(i)]) { | |
209 | i = (BIT_WORD(i) + 1) * BITS_PER_LONG - gpio_reg_size; | |
210 | continue; | |
211 | } | |
212 | ||
213 | port = i / gpio_reg_size; | |
214 | ||
215 | /* mask out GPIO configured for input */ | |
216 | iomask = mask[BIT_WORD(i)] & ~ws16c48gpio->io_state[port]; | |
217 | bitmask = iomask & bits[BIT_WORD(i)]; | |
218 | ||
a0a584f0 | 219 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
99c8ac95 WBG |
220 | |
221 | /* update output state data and set device gpio register */ | |
222 | ws16c48gpio->out_state[port] &= ~iomask; | |
223 | ws16c48gpio->out_state[port] |= bitmask; | |
224 | outb(ws16c48gpio->out_state[port], ws16c48gpio->base + port); | |
225 | ||
a0a584f0 | 226 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
99c8ac95 WBG |
227 | |
228 | /* prepare for next gpio register set */ | |
229 | mask[BIT_WORD(i)] >>= gpio_reg_size; | |
230 | bits[BIT_WORD(i)] >>= gpio_reg_size; | |
231 | } | |
232 | } | |
233 | ||
9c26df9b WBG |
234 | static void ws16c48_irq_ack(struct irq_data *data) |
235 | { | |
236 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | |
237 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
238 | const unsigned long offset = irqd_to_hwirq(data); | |
239 | const unsigned port = offset / 8; | |
240 | const unsigned mask = BIT(offset % 8); | |
241 | unsigned long flags; | |
242 | unsigned port_state; | |
243 | ||
244 | /* only the first 3 ports support interrupts */ | |
245 | if (port > 2) | |
246 | return; | |
247 | ||
a0a584f0 | 248 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
249 | |
250 | port_state = ws16c48gpio->irq_mask >> (8*port); | |
251 | ||
252 | outb(0x80, ws16c48gpio->base + 7); | |
253 | outb(port_state & ~mask, ws16c48gpio->base + 8 + port); | |
254 | outb(port_state | mask, ws16c48gpio->base + 8 + port); | |
255 | outb(0xC0, ws16c48gpio->base + 7); | |
256 | ||
a0a584f0 | 257 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
258 | } |
259 | ||
260 | static void ws16c48_irq_mask(struct irq_data *data) | |
261 | { | |
262 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | |
263 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
264 | const unsigned long offset = irqd_to_hwirq(data); | |
265 | const unsigned long mask = BIT(offset); | |
266 | const unsigned port = offset / 8; | |
267 | unsigned long flags; | |
268 | ||
269 | /* only the first 3 ports support interrupts */ | |
270 | if (port > 2) | |
271 | return; | |
272 | ||
a0a584f0 | 273 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
274 | |
275 | ws16c48gpio->irq_mask &= ~mask; | |
276 | ||
277 | outb(0x80, ws16c48gpio->base + 7); | |
278 | outb(ws16c48gpio->irq_mask >> (8*port), ws16c48gpio->base + 8 + port); | |
279 | outb(0xC0, ws16c48gpio->base + 7); | |
280 | ||
a0a584f0 | 281 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
282 | } |
283 | ||
284 | static void ws16c48_irq_unmask(struct irq_data *data) | |
285 | { | |
286 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | |
287 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
288 | const unsigned long offset = irqd_to_hwirq(data); | |
289 | const unsigned long mask = BIT(offset); | |
290 | const unsigned port = offset / 8; | |
291 | unsigned long flags; | |
292 | ||
293 | /* only the first 3 ports support interrupts */ | |
294 | if (port > 2) | |
295 | return; | |
296 | ||
a0a584f0 | 297 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
298 | |
299 | ws16c48gpio->irq_mask |= mask; | |
300 | ||
301 | outb(0x80, ws16c48gpio->base + 7); | |
302 | outb(ws16c48gpio->irq_mask >> (8*port), ws16c48gpio->base + 8 + port); | |
303 | outb(0xC0, ws16c48gpio->base + 7); | |
304 | ||
a0a584f0 | 305 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
306 | } |
307 | ||
308 | static int ws16c48_irq_set_type(struct irq_data *data, unsigned flow_type) | |
309 | { | |
310 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | |
311 | struct ws16c48_gpio *const ws16c48gpio = gpiochip_get_data(chip); | |
312 | const unsigned long offset = irqd_to_hwirq(data); | |
313 | const unsigned long mask = BIT(offset); | |
314 | const unsigned port = offset / 8; | |
315 | unsigned long flags; | |
316 | ||
317 | /* only the first 3 ports support interrupts */ | |
318 | if (port > 2) | |
319 | return -EINVAL; | |
320 | ||
a0a584f0 | 321 | raw_spin_lock_irqsave(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
322 | |
323 | switch (flow_type) { | |
324 | case IRQ_TYPE_NONE: | |
325 | break; | |
326 | case IRQ_TYPE_EDGE_RISING: | |
327 | ws16c48gpio->flow_mask |= mask; | |
328 | break; | |
329 | case IRQ_TYPE_EDGE_FALLING: | |
330 | ws16c48gpio->flow_mask &= ~mask; | |
331 | break; | |
332 | default: | |
a0a584f0 | 333 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
334 | return -EINVAL; |
335 | } | |
336 | ||
337 | outb(0x40, ws16c48gpio->base + 7); | |
338 | outb(ws16c48gpio->flow_mask >> (8*port), ws16c48gpio->base + 8 + port); | |
339 | outb(0xC0, ws16c48gpio->base + 7); | |
340 | ||
a0a584f0 | 341 | raw_spin_unlock_irqrestore(&ws16c48gpio->lock, flags); |
9c26df9b WBG |
342 | |
343 | return 0; | |
344 | } | |
345 | ||
346 | static struct irq_chip ws16c48_irqchip = { | |
347 | .name = "ws16c48", | |
348 | .irq_ack = ws16c48_irq_ack, | |
349 | .irq_mask = ws16c48_irq_mask, | |
350 | .irq_unmask = ws16c48_irq_unmask, | |
351 | .irq_set_type = ws16c48_irq_set_type | |
352 | }; | |
353 | ||
354 | static irqreturn_t ws16c48_irq_handler(int irq, void *dev_id) | |
355 | { | |
356 | struct ws16c48_gpio *const ws16c48gpio = dev_id; | |
357 | struct gpio_chip *const chip = &ws16c48gpio->chip; | |
358 | unsigned long int_pending; | |
359 | unsigned long port; | |
360 | unsigned long int_id; | |
361 | unsigned long gpio; | |
362 | ||
363 | int_pending = inb(ws16c48gpio->base + 6) & 0x7; | |
364 | if (!int_pending) | |
365 | return IRQ_NONE; | |
366 | ||
367 | /* loop until all pending interrupts are handled */ | |
368 | do { | |
369 | for_each_set_bit(port, &int_pending, 3) { | |
370 | int_id = inb(ws16c48gpio->base + 8 + port); | |
371 | for_each_set_bit(gpio, &int_id, 8) | |
372 | generic_handle_irq(irq_find_mapping( | |
f0fbe7bc | 373 | chip->irq.domain, gpio + 8*port)); |
9c26df9b WBG |
374 | } |
375 | ||
376 | int_pending = inb(ws16c48gpio->base + 6) & 0x7; | |
377 | } while (int_pending); | |
378 | ||
379 | return IRQ_HANDLED; | |
380 | } | |
381 | ||
5238f60f WBG |
382 | #define WS16C48_NGPIO 48 |
383 | static const char *ws16c48_names[WS16C48_NGPIO] = { | |
384 | "Port 0 Bit 0", "Port 0 Bit 1", "Port 0 Bit 2", "Port 0 Bit 3", | |
385 | "Port 0 Bit 4", "Port 0 Bit 5", "Port 0 Bit 6", "Port 0 Bit 7", | |
386 | "Port 1 Bit 0", "Port 1 Bit 1", "Port 1 Bit 2", "Port 1 Bit 3", | |
387 | "Port 1 Bit 4", "Port 1 Bit 5", "Port 1 Bit 6", "Port 1 Bit 7", | |
388 | "Port 2 Bit 0", "Port 2 Bit 1", "Port 2 Bit 2", "Port 2 Bit 3", | |
389 | "Port 2 Bit 4", "Port 2 Bit 5", "Port 2 Bit 6", "Port 2 Bit 7", | |
390 | "Port 3 Bit 0", "Port 3 Bit 1", "Port 3 Bit 2", "Port 3 Bit 3", | |
391 | "Port 3 Bit 4", "Port 3 Bit 5", "Port 3 Bit 6", "Port 3 Bit 7", | |
392 | "Port 4 Bit 0", "Port 4 Bit 1", "Port 4 Bit 2", "Port 4 Bit 3", | |
393 | "Port 4 Bit 4", "Port 4 Bit 5", "Port 4 Bit 6", "Port 4 Bit 7", | |
394 | "Port 5 Bit 0", "Port 5 Bit 1", "Port 5 Bit 2", "Port 5 Bit 3", | |
395 | "Port 5 Bit 4", "Port 5 Bit 5", "Port 5 Bit 6", "Port 5 Bit 7" | |
396 | }; | |
397 | ||
cc736607 | 398 | static int ws16c48_probe(struct device *dev, unsigned int id) |
9c26df9b | 399 | { |
9c26df9b | 400 | struct ws16c48_gpio *ws16c48gpio; |
9c26df9b WBG |
401 | const char *const name = dev_name(dev); |
402 | int err; | |
9c26df9b WBG |
403 | |
404 | ws16c48gpio = devm_kzalloc(dev, sizeof(*ws16c48gpio), GFP_KERNEL); | |
405 | if (!ws16c48gpio) | |
406 | return -ENOMEM; | |
407 | ||
cc736607 | 408 | if (!devm_request_region(dev, base[id], WS16C48_EXTENT, name)) { |
148ad68b | 409 | dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", |
cc736607 | 410 | base[id], base[id] + WS16C48_EXTENT); |
148ad68b | 411 | return -EBUSY; |
9c26df9b WBG |
412 | } |
413 | ||
414 | ws16c48gpio->chip.label = name; | |
415 | ws16c48gpio->chip.parent = dev; | |
416 | ws16c48gpio->chip.owner = THIS_MODULE; | |
417 | ws16c48gpio->chip.base = -1; | |
5238f60f WBG |
418 | ws16c48gpio->chip.ngpio = WS16C48_NGPIO; |
419 | ws16c48gpio->chip.names = ws16c48_names; | |
9c26df9b WBG |
420 | ws16c48gpio->chip.get_direction = ws16c48_gpio_get_direction; |
421 | ws16c48gpio->chip.direction_input = ws16c48_gpio_direction_input; | |
422 | ws16c48gpio->chip.direction_output = ws16c48_gpio_direction_output; | |
423 | ws16c48gpio->chip.get = ws16c48_gpio_get; | |
a8ff510d | 424 | ws16c48gpio->chip.get_multiple = ws16c48_gpio_get_multiple; |
9c26df9b | 425 | ws16c48gpio->chip.set = ws16c48_gpio_set; |
99c8ac95 | 426 | ws16c48gpio->chip.set_multiple = ws16c48_gpio_set_multiple; |
cc736607 | 427 | ws16c48gpio->base = base[id]; |
9c26df9b | 428 | |
a0a584f0 | 429 | raw_spin_lock_init(&ws16c48gpio->lock); |
9c26df9b | 430 | |
b4cad1bc | 431 | err = devm_gpiochip_add_data(dev, &ws16c48gpio->chip, ws16c48gpio); |
9c26df9b WBG |
432 | if (err) { |
433 | dev_err(dev, "GPIO registering failed (%d)\n", err); | |
148ad68b | 434 | return err; |
9c26df9b WBG |
435 | } |
436 | ||
437 | /* Disable IRQ by default */ | |
cc736607 WBG |
438 | outb(0x80, base[id] + 7); |
439 | outb(0, base[id] + 8); | |
440 | outb(0, base[id] + 9); | |
441 | outb(0, base[id] + 10); | |
442 | outb(0xC0, base[id] + 7); | |
9c26df9b WBG |
443 | |
444 | err = gpiochip_irqchip_add(&ws16c48gpio->chip, &ws16c48_irqchip, 0, | |
445 | handle_edge_irq, IRQ_TYPE_NONE); | |
446 | if (err) { | |
447 | dev_err(dev, "Could not add irqchip (%d)\n", err); | |
b4cad1bc | 448 | return err; |
9c26df9b WBG |
449 | } |
450 | ||
b4cad1bc WBG |
451 | err = devm_request_irq(dev, irq[id], ws16c48_irq_handler, IRQF_SHARED, |
452 | name, ws16c48gpio); | |
9c26df9b WBG |
453 | if (err) { |
454 | dev_err(dev, "IRQ handler registering failed (%d)\n", err); | |
b4cad1bc | 455 | return err; |
9c26df9b WBG |
456 | } |
457 | ||
9c26df9b WBG |
458 | return 0; |
459 | } | |
460 | ||
cc736607 WBG |
461 | static struct isa_driver ws16c48_driver = { |
462 | .probe = ws16c48_probe, | |
9c26df9b WBG |
463 | .driver = { |
464 | .name = "ws16c48" | |
465 | }, | |
9c26df9b WBG |
466 | }; |
467 | ||
cc736607 | 468 | module_isa_driver(ws16c48_driver, num_ws16c48); |
9c26df9b WBG |
469 | |
470 | MODULE_AUTHOR("William Breathitt Gray <vilhelm.gray@gmail.com>"); | |
471 | MODULE_DESCRIPTION("WinSystems WS16C48 GPIO driver"); | |
22aeddb5 | 472 | MODULE_LICENSE("GPL v2"); |