summaryrefslogtreecommitdiff
path: root/drivers/isdn/hardware/mISDN/speedfax.c
blob: 0c405261d94069f177cd4950561bd9e641c7d036 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
// SPDX-License-Identifier: GPL-2.0-only
/*
 * speedfax.c	low level stuff for Sedlbauer Speedfax+ cards
 *		based on the ISAR DSP
 *		Thanks to Sedlbauer AG for informations and HW
 *
 * Author       Karsten Keil <keil@isdn4linux.de>
 *
 * Copyright 2009  by Karsten Keil <keil@isdn4linux.de>
 */

#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/mISDNhw.h>
#include <linux/firmware.h>
#include "ipac.h"
#include "isar.h"

#define SPEEDFAX_REV	"2.0"

#define PCI_SUBVENDOR_SPEEDFAX_PYRAMID	0x51
#define PCI_SUBVENDOR_SPEEDFAX_PCI	0x54
#define PCI_SUB_ID_SEDLBAUER		0x01

#define SFAX_PCI_ADDR		0xc8
#define SFAX_PCI_ISAC		0xd0
#define SFAX_PCI_ISAR		0xe0

/* TIGER 100 Registers */

#define TIGER_RESET_ADDR	0x00
#define TIGER_EXTERN_RESET_ON	0x01
#define TIGER_EXTERN_RESET_OFF	0x00
#define TIGER_AUX_CTRL		0x02
#define TIGER_AUX_DATA		0x03
#define TIGER_AUX_IRQMASK	0x05
#define TIGER_AUX_STATUS	0x07

/* Tiger AUX BITs */
#define SFAX_AUX_IOMASK		0xdd	/* 1 and 5 are inputs */
#define SFAX_ISAR_RESET_BIT_OFF 0x00
#define SFAX_ISAR_RESET_BIT_ON	0x01
#define SFAX_TIGER_IRQ_BIT	0x02
#define SFAX_LED1_BIT		0x08
#define SFAX_LED2_BIT		0x10

#define SFAX_PCI_RESET_ON	(SFAX_ISAR_RESET_BIT_ON)
#define SFAX_PCI_RESET_OFF	(SFAX_LED1_BIT | SFAX_LED2_BIT)

static int sfax_cnt;
static u32 debug;
static u32 irqloops = 4;

struct sfax_hw {
	struct list_head	list;
	struct pci_dev		*pdev;
	char			name[MISDN_MAX_IDLEN];
	u32			irq;
	u32			irqcnt;
	u32			cfg;
	struct _ioport		p_isac;
	struct _ioport		p_isar;
	u8			aux_data;
	spinlock_t		lock;	/* HW access lock */
	struct isac_hw		isac;
	struct isar_hw		isar;
};

static LIST_HEAD(Cards);
static DEFINE_RWLOCK(card_lock); /* protect Cards */

static void
_set_debug(struct sfax_hw *card)
{
	card->isac.dch.debug = debug;
	card->isar.ch[0].bch.debug = debug;
	card->isar.ch[1].bch.debug = debug;
}

static int
set_debug(const char *val, const struct kernel_param *kp)
{
	int ret;
	struct sfax_hw *card;

	ret = param_set_uint(val, kp);
	if (!ret) {
		read_lock(&card_lock);
		list_for_each_entry(card, &Cards, list)
			_set_debug(card);
		read_unlock(&card_lock);
	}
	return ret;
}

MODULE_AUTHOR("Karsten Keil");
MODULE_DESCRIPTION("mISDN driver for Sedlbauer Speedfax+ cards");
MODULE_LICENSE("GPL v2");
MODULE_VERSION(SPEEDFAX_REV);
MODULE_FIRMWARE("isdn/ISAR.BIN");
module_param_call(debug, set_debug, param_get_uint, &debug, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Speedfax debug mask");
module_param(irqloops, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(irqloops, "Speedfax maximal irqloops (default 4)");

IOFUNC_IND(ISAC, sfax_hw, p_isac)
IOFUNC_IND(ISAR, sfax_hw, p_isar)

static irqreturn_t
speedfax_irq(int intno, void *dev_id)
{
	struct sfax_hw	*sf = dev_id;
	u8 val;
	int cnt = irqloops;

	spin_lock(&sf->lock);
	val = inb(sf->cfg + TIGER_AUX_STATUS);
	if (val & SFAX_TIGER_IRQ_BIT) { /* for us or shared ? */
		spin_unlock(&sf->lock);
		return IRQ_NONE; /* shared */
	}
	sf->irqcnt++;
	val = ReadISAR_IND(sf, ISAR_IRQBIT);
Start_ISAR:
	if (val & ISAR_IRQSTA)
		mISDNisar_irq(&sf->isar);
	val = ReadISAC_IND(sf, ISAC_ISTA);
	if (val)
		mISDNisac_irq(&sf->isac, val);
	val = ReadISAR_IND(sf, ISAR_IRQBIT);
	if ((val & ISAR_IRQSTA) && cnt--)
		goto Start_ISAR;
	if (cnt < irqloops)
		pr_debug("%s: %d irqloops cpu%d\n", sf->name,
			 irqloops - cnt, smp_processor_id());
	if (irqloops && !cnt)
		pr_notice("%s: %d IRQ LOOP cpu%d\n", sf->name,
			  irqloops, smp_processor_id());
	spin_unlock(&sf->lock);
	return IRQ_HANDLED;
}

static void
enable_hwirq(struct sfax_hw *sf)
{
	WriteISAC_IND(sf, ISAC_MASK, 0);
	WriteISAR_IND(sf, ISAR_IRQBIT, ISAR_IRQMSK);
	outb(SFAX_TIGER_IRQ_BIT, sf->cfg + TIGER_AUX_IRQMASK);
}

static void
disable_hwirq(struct sfax_hw *sf)
{
	WriteISAC_IND(sf, ISAC_MASK, 0xFF);
	WriteISAR_IND(sf, ISAR_IRQBIT, 0);
	outb(0, sf->cfg + TIGER_AUX_IRQMASK);
}

static void
reset_speedfax(struct sfax_hw *sf)
{

	pr_debug("%s: resetting card\n", sf->name);
	outb(TIGER_EXTERN_RESET_ON, sf->cfg + TIGER_RESET_ADDR);
	outb(SFAX_PCI_RESET_ON, sf->cfg + TIGER_AUX_DATA);
	mdelay(1);
	outb(TIGER_EXTERN_RESET_OFF, sf->cfg + TIGER_RESET_ADDR);
	sf->aux_data = SFAX_PCI_RESET_OFF;
	outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA);
	mdelay(1);
}

static int
sfax_ctrl(struct sfax_hw  *sf, u32 cmd, u_long arg)
{
	int ret = 0;

	switch (cmd) {
	case HW_RESET_REQ:
		reset_speedfax(sf);
		break;
	case HW_ACTIVATE_IND:
		if (arg & 1)
			sf->aux_data &= ~SFAX_LED1_BIT;
		if (arg & 2)
			sf->aux_data &= ~SFAX_LED2_BIT;
		outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA);
		break;
	case HW_DEACT_IND:
		if (arg & 1)
			sf->aux_data |= SFAX_LED1_BIT;
		if (arg & 2)
			sf->aux_data |= SFAX_LED2_BIT;
		outb(sf->aux_data, sf->cfg + TIGER_AUX_DATA);
		break;
	default:
		pr_info("%s: %s unknown command %x %lx\n",
			sf->name, __func__, cmd, arg);
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int
channel_ctrl(struct sfax_hw  *sf, struct mISDN_ctrl_req *cq)
{
	int	ret = 0;

	switch (cq->op) {
	case MISDN_CTRL_GETOP:
		cq->op = MISDN_CTRL_LOOP | MISDN_CTRL_L1_TIMER3;
		break;
	case MISDN_CTRL_LOOP:
		/* cq->channel: 0 disable, 1 B1 loop 2 B2 loop, 3 both */
		if (cq->channel < 0 || cq->channel > 3) {
			ret = -EINVAL;
			break;
		}
		ret = sf->isac.ctrl(&sf->isac, HW_TESTLOOP, cq->channel);
		break;
	case MISDN_CTRL_L1_TIMER3:
		ret = sf->isac.ctrl(&sf->isac, HW_TIMER3_VALUE, cq->p1);
		break;
	default:
		pr_info("%s: unknown Op %x\n", sf->name, cq->op);
		ret = -EINVAL;
		break;
	}
	return ret;
}

static int
sfax_dctrl(struct mISDNchannel *ch, u32 cmd, void *arg)
{
	struct mISDNdevice	*dev = container_of(ch, struct mISDNdevice, D);
	struct dchannel		*dch = container_of(dev, struct dchannel, dev);
	struct sfax_hw		*sf = dch->hw;
	struct channel_req	*rq;
	int			err = 0;

	pr_debug("%s: cmd:%x %p\n", sf->name, cmd, arg);
	switch (cmd) {
	case OPEN_CHANNEL:
		rq = arg;
		if (rq->protocol == ISDN_P_TE_S0)
			err = sf->isac.open(&sf->isac, rq);
		else
			err = sf->isar.open(&sf->isar, rq);
		if (err)
			break;
		if (!try_module_get(THIS_MODULE))
			pr_info("%s: cannot get module\n", sf->name);
		break;
	case CLOSE_CHANNEL:
		pr_debug("%s: dev(%d) close from %p\n", sf->name,
			 dch->dev.id, __builtin_return_address(0));
		module_put(THIS_MODULE);
		break;
	case CONTROL_CHANNEL:
		err = channel_ctrl(sf, arg);
		break;
	default:
		pr_debug("%s: unknown command %x\n", sf->name, cmd);
		return -EINVAL;
	}
	return err;
}

static int
init_card(struct sfax_hw *sf)
{
	int	ret, cnt = 3;
	u_long	flags;

	ret = request_irq(sf->irq, speedfax_irq, IRQF_SHARED, sf->name, sf);
	if (ret) {
		pr_info("%s: couldn't get interrupt %d\n", sf->name, sf->irq);
		return ret;
	}
	while (cnt--) {
		spin_lock_irqsave(&sf->lock, flags);
		ret = sf->isac.init(&sf->isac);
		if (ret) {
			spin_unlock_irqrestore(&sf->lock, flags);
			pr_info("%s: ISAC init failed with %d\n",
				sf->name, ret);
			break;
		}
		enable_hwirq(sf);
		/* RESET Receiver and Transmitter */
		WriteISAC_IND(sf, ISAC_CMDR, 0x41);
		spin_unlock_irqrestore(&sf->lock, flags);
		msleep_interruptible(10);
		if (debug & DEBUG_HW)
			pr_notice("%s: IRQ %d count %d\n", sf->name,
				  sf->irq, sf->irqcnt);
		if (!sf->irqcnt) {
			pr_info("%s: IRQ(%d) got no requests during init %d\n",
				sf->name, sf->irq, 3 - cnt);
		} else
			return 0;
	}
	free_irq(sf->irq, sf);
	return -EIO;
}


static int
setup_speedfax(struct sfax_hw *sf)
{
	u_long flags;

	if (!request_region(sf->cfg, 256, sf->name)) {
		pr_info("mISDN: %s config port %x-%x already in use\n",
			sf->name, sf->cfg, sf->cfg + 255);
		return -EIO;
	}
	outb(0xff, sf->cfg);
	outb(0, sf->cfg);
	outb(0xdd, sf->cfg + TIGER_AUX_CTRL);
	outb(0, sf->cfg + TIGER_AUX_IRQMASK);

	sf->isac.type = IPAC_TYPE_ISAC;
	sf->p_isac.ale = sf->cfg + SFAX_PCI_ADDR;
	sf->p_isac.port = sf->cfg + SFAX_PCI_ISAC;
	sf->p_isar.ale = sf->cfg + SFAX_PCI_ADDR;
	sf->p_isar.port = sf->cfg + SFAX_PCI_ISAR;
	ASSIGN_FUNC(IND, ISAC, sf->isac);
	ASSIGN_FUNC(IND, ISAR, sf->isar);
	spin_lock_irqsave(&sf->lock, flags);
	reset_speedfax(sf);
	disable_hwirq(sf);
	spin_unlock_irqrestore(&sf->lock, flags);
	return 0;
}

static void
release_card(struct sfax_hw *card) {
	u_long	flags;

	spin_lock_irqsave(&card->lock, flags);
	disable_hwirq(card);
	spin_unlock_irqrestore(&card->lock, flags);
	card->isac.release(&card->isac);
	free_irq(card->irq, card);
	card->isar.release(&card->isar);
	mISDN_unregister_device(&card->isac.dch.dev);
	release_region(card->cfg, 256);
	pci_disable_device(card->pdev);
	pci_set_drvdata(card->pdev, NULL);
	write_lock_irqsave(&card_lock, flags);
	list_del(&card->list);
	write_unlock_irqrestore(&card_lock, flags);
	kfree(card);
	sfax_cnt--;
}

static int
setup_instance(struct sfax_hw *card)
{
	const struct firmware *firmware;
	int i, err;
	u_long flags;

	snprintf(card->name, MISDN_MAX_IDLEN - 1, "Speedfax.%d", sfax_cnt + 1);
	write_lock_irqsave(&card_lock, flags);
	list_add_tail(&card->list, &Cards);
	write_unlock_irqrestore(&card_lock, flags);
	_set_debug(card);
	spin_lock_init(&card->lock);
	card->isac.hwlock = &card->lock;
	card->isar.hwlock = &card->lock;
	card->isar.ctrl = (void *)&sfax_ctrl;
	card->isac.name = card->name;
	card->isar.name = card->name;
	card->isar.owner = THIS_MODULE;

	err = request_firmware(&firmware, "isdn/ISAR.BIN", &card->pdev->dev);
	if (err < 0) {
		pr_info("%s: firmware request failed %d\n",
			card->name, err);
		goto error_fw;
	}
	if (debug & DEBUG_HW)
		pr_notice("%s: got firmware %zu bytes\n",
			  card->name, firmware->size);

	mISDNisac_init(&card->isac, card);

	card->isac.dch.dev.D.ctrl = sfax_dctrl;
	card->isac.dch.dev.Bprotocols =
		mISDNisar_init(&card->isar, card);
	for (i = 0; i < 2; i++) {
		set_channelmap(i + 1, card->isac.dch.dev.channelmap);
		list_add(&card->isar.ch[i].bch.ch.list,
			 &card->isac.dch.dev.bchannels);
	}

	err = setup_speedfax(card);
	if (err)
		goto error_setup;
	err = card->isar.init(&card->isar);
	if (err)
		goto error;
	err = mISDN_register_device(&card->isac.dch.dev,
				    &card->pdev->dev, card->name);
	if (err)
		goto error;
	err = init_card(card);
	if (err)
		goto error_init;
	err = card->isar.firmware(&card->isar, firmware->data, firmware->size);
	if (!err)  {
		release_firmware(firmware);
		sfax_cnt++;
		pr_notice("SpeedFax %d cards installed\n", sfax_cnt);
		return 0;
	}
	disable_hwirq(card);
	free_irq(card->irq, card);
error_init:
	mISDN_unregister_device(&card->isac.dch.dev);
error:
	release_region(card->cfg, 256);
error_setup:
	card->isac.release(&card->isac);
	card->isar.release(&card->isar);
	release_firmware(firmware);
error_fw:
	pci_disable_device(card->pdev);
	write_lock_irqsave(&card_lock, flags);
	list_del(&card->list);
	write_unlock_irqrestore(&card_lock, flags);
	kfree(card);
	return err;
}

static int
sfaxpci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	int err = -ENOMEM;
	struct sfax_hw *card = kzalloc(sizeof(struct sfax_hw), GFP_KERNEL);

	if (!card) {
		pr_info("No memory for Speedfax+ PCI\n");
		return err;
	}
	card->pdev = pdev;
	err = pci_enable_device(pdev);
	if (err) {
		kfree(card);
		return err;
	}

	pr_notice("mISDN: Speedfax found adapter %s at %s\n",
		  (char *)ent->driver_data, pci_name(pdev));

	card->cfg = pci_resource_start(pdev, 0);
	card->irq = pdev->irq;
	pci_set_drvdata(pdev, card);
	err = setup_instance(card);
	if (err)
		pci_set_drvdata(pdev, NULL);
	return err;
}

static void
sfax_remove_pci(struct pci_dev *pdev)
{
	struct sfax_hw	*card = pci_get_drvdata(pdev);

	if (card)
		release_card(card);
	else
		pr_debug("%s: drvdata already removed\n", __func__);
}

static struct pci_device_id sfaxpci_ids[] = {
	{ PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100,
	  PCI_SUBVENDOR_SPEEDFAX_PYRAMID, PCI_SUB_ID_SEDLBAUER,
	  0, 0, (unsigned long) "Pyramid Speedfax + PCI"
	},
	{ PCI_VENDOR_ID_TIGERJET, PCI_DEVICE_ID_TIGERJET_100,
	  PCI_SUBVENDOR_SPEEDFAX_PCI, PCI_SUB_ID_SEDLBAUER,
	  0, 0, (unsigned long) "Sedlbauer Speedfax + PCI"
	},
	{ }
};
MODULE_DEVICE_TABLE(pci, sfaxpci_ids);

static struct pci_driver sfaxpci_driver = {
	.name = "speedfax+ pci",
	.probe = sfaxpci_probe,
	.remove = sfax_remove_pci,
	.id_table = sfaxpci_ids,
};

static int __init
Speedfax_init(void)
{
	int err;

	pr_notice("Sedlbauer Speedfax+ Driver Rev. %s\n",
		  SPEEDFAX_REV);
	err = pci_register_driver(&sfaxpci_driver);
	return err;
}

static void __exit
Speedfax_cleanup(void)
{
	pci_unregister_driver(&sfaxpci_driver);
}

module_init(Speedfax_init);
module_exit(Speedfax_cleanup);