summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/intersil/hostap/hostap_cs.c
blob: 1a748670835ac0bf6014209a07aa5ce77b4d3a9f (plain) (blame)
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
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
// SPDX-License-Identifier: GPL-2.0-only
#define PRISM2_PCCARD

#include <linux/module.h>
#include <linux/if.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/workqueue.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>

#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ds.h>

#include <asm/io.h>

#include "hostap_wlan.h"


static char *dev_info = "hostap_cs";

MODULE_AUTHOR("Jouni Malinen");
MODULE_DESCRIPTION("Support for Intersil Prism2-based 802.11 wireless LAN "
		   "cards (PC Card).");
MODULE_SUPPORTED_DEVICE("Intersil Prism2-based WLAN cards (PC Card)");
MODULE_LICENSE("GPL");


static int ignore_cis_vcc;
module_param(ignore_cis_vcc, int, 0444);
MODULE_PARM_DESC(ignore_cis_vcc, "Ignore broken CIS VCC entry");


/* struct local_info::hw_priv */
struct hostap_cs_priv {
	struct pcmcia_device *link;
	int sandisk_connectplus;
};


#ifdef PRISM2_IO_DEBUG

static inline void hfa384x_outb_debug(struct net_device *dev, int a, u8 v)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTB, a, v);
	outb(v, dev->base_addr + a);
	spin_unlock_irqrestore(&local->lock, flags);
}

static inline u8 hfa384x_inb_debug(struct net_device *dev, int a)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;
	u8 v;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	v = inb(dev->base_addr + a);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INB, a, v);
	spin_unlock_irqrestore(&local->lock, flags);
	return v;
}

static inline void hfa384x_outw_debug(struct net_device *dev, int a, u16 v)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTW, a, v);
	outw(v, dev->base_addr + a);
	spin_unlock_irqrestore(&local->lock, flags);
}

static inline u16 hfa384x_inw_debug(struct net_device *dev, int a)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;
	u16 v;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	v = inw(dev->base_addr + a);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INW, a, v);
	spin_unlock_irqrestore(&local->lock, flags);
	return v;
}

static inline void hfa384x_outsw_debug(struct net_device *dev, int a,
				       u8 *buf, int wc)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_OUTSW, a, wc);
	outsw(dev->base_addr + a, buf, wc);
	spin_unlock_irqrestore(&local->lock, flags);
}

static inline void hfa384x_insw_debug(struct net_device *dev, int a,
				      u8 *buf, int wc)
{
	struct hostap_interface *iface;
	local_info_t *local;
	unsigned long flags;

	iface = netdev_priv(dev);
	local = iface->local;
	spin_lock_irqsave(&local->lock, flags);
	prism2_io_debug_add(dev, PRISM2_IO_DEBUG_CMD_INSW, a, wc);
	insw(dev->base_addr + a, buf, wc);
	spin_unlock_irqrestore(&local->lock, flags);
}

#define HFA384X_OUTB(v,a) hfa384x_outb_debug(dev, (a), (v))
#define HFA384X_INB(a) hfa384x_inb_debug(dev, (a))
#define HFA384X_OUTW(v,a) hfa384x_outw_debug(dev, (a), (v))
#define HFA384X_INW(a) hfa384x_inw_debug(dev, (a))
#define HFA384X_OUTSW(a, buf, wc) hfa384x_outsw_debug(dev, (a), (buf), (wc))
#define HFA384X_INSW(a, buf, wc) hfa384x_insw_debug(dev, (a), (buf), (wc))

#else /* PRISM2_IO_DEBUG */

#define HFA384X_OUTB(v,a) outb((v), dev->base_addr + (a))
#define HFA384X_INB(a) inb(dev->base_addr + (a))
#define HFA384X_OUTW(v,a) outw((v), dev->base_addr + (a))
#define HFA384X_INW(a) inw(dev->base_addr + (a))
#define HFA384X_INSW(a, buf, wc) insw(dev->base_addr + (a), buf, wc)
#define HFA384X_OUTSW(a, buf, wc) outsw(dev->base_addr + (a), buf, wc)

#endif /* PRISM2_IO_DEBUG */


static int hfa384x_from_bap(struct net_device *dev, u16 bap, void *buf,
			    int len)
{
	u16 d_off;
	u16 *pos;

	d_off = (bap == 1) ? HFA384X_DATA1_OFF : HFA384X_DATA0_OFF;
	pos = (u16 *) buf;

	if (len / 2)
		HFA384X_INSW(d_off, buf, len / 2);
	pos += len / 2;

	if (len & 1)
		*((char *) pos) = HFA384X_INB(d_off);

	return 0;
}


static int hfa384x_to_bap(struct net_device *dev, u16 bap, void *buf, int len)
{
	u16 d_off;
	u16 *pos;

	d_off = (bap == 1) ? HFA384X_DATA1_OFF : HFA384X_DATA0_OFF;
	pos = (u16 *) buf;

	if (len / 2)
		HFA384X_OUTSW(d_off, buf, len / 2);
	pos += len / 2;

	if (len & 1)
		HFA384X_OUTB(*((char *) pos), d_off);

	return 0;
}


/* FIX: This might change at some point.. */
#include "hostap_hw.c"



static void prism2_detach(struct pcmcia_device *p_dev);
static void prism2_release(u_long arg);
static int prism2_config(struct pcmcia_device *link);


static int prism2_pccard_card_present(local_info_t *local)
{
	struct hostap_cs_priv *hw_priv = local->hw_priv;
	if (hw_priv != NULL && hw_priv->link != NULL && pcmcia_dev_present(hw_priv->link))
		return 1;
	return 0;
}


/*
 * SanDisk CompactFlash WLAN Flashcard - Product Manual v1.0
 * Document No. 20-10-00058, January 2004
 * http://www.sandisk.com/pdf/industrial/ProdManualCFWLANv1.0.pdf
 */
#define SANDISK_WLAN_ACTIVATION_OFF 0x40
#define SANDISK_HCR_OFF 0x42


static void sandisk_set_iobase(local_info_t *local)
{
	int res;
	struct hostap_cs_priv *hw_priv = local->hw_priv;

	res = pcmcia_write_config_byte(hw_priv->link, 0x10,
				hw_priv->link->resource[0]->start & 0x00ff);
	if (res != 0) {
		printk(KERN_DEBUG "Prism3 SanDisk - failed to set I/O base 0 -"
		       " res=%d\n", res);
	}
	udelay(10);

	res = pcmcia_write_config_byte(hw_priv->link, 0x12,
				(hw_priv->link->resource[0]->start >> 8) & 0x00ff);
	if (res != 0) {
		printk(KERN_DEBUG "Prism3 SanDisk - failed to set I/O base 1 -"
		       " res=%d\n", res);
	}
}


static void sandisk_write_hcr(local_info_t *local, int hcr)
{
	struct net_device *dev = local->dev;
	int i;

	HFA384X_OUTB(0x80, SANDISK_WLAN_ACTIVATION_OFF);
	udelay(50);
	for (i = 0; i < 10; i++) {
		HFA384X_OUTB(hcr, SANDISK_HCR_OFF);
	}
	udelay(55);
	HFA384X_OUTB(0x45, SANDISK_WLAN_ACTIVATION_OFF);
}


static int sandisk_enable_wireless(struct net_device *dev)
{
	int res, ret = 0;
	struct hostap_interface *iface = netdev_priv(dev);
	local_info_t *local = iface->local;
	struct hostap_cs_priv *hw_priv = local->hw_priv;

	if (resource_size(hw_priv->link->resource[0]) < 0x42) {
		/* Not enough ports to be SanDisk multi-function card */
		ret = -ENODEV;
		goto done;
	}

	if (hw_priv->link->manf_id != 0xd601 || hw_priv->link->card_id != 0x0101) {
		/* No SanDisk manfid found */
		ret = -ENODEV;
		goto done;
	}

	if (hw_priv->link->socket->functions < 2) {
		/* No multi-function links found */
		ret = -ENODEV;
		goto done;
	}

	printk(KERN_DEBUG "%s: Multi-function SanDisk ConnectPlus detected"
	       " - using vendor-specific initialization\n", dev->name);
	hw_priv->sandisk_connectplus = 1;

	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR,
				COR_SOFT_RESET);
	if (res != 0) {
		printk(KERN_DEBUG "%s: SanDisk - COR sreset failed (%d)\n",
		       dev->name, res);
		goto done;
	}
	mdelay(5);

	/*
	 * Do not enable interrupts here to avoid some bogus events. Interrupts
	 * will be enabled during the first cor_sreset call.
	 */
	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR,
				(COR_LEVEL_REQ | 0x8 | COR_ADDR_DECODE |
					COR_FUNC_ENA));
	if (res != 0) {
		printk(KERN_DEBUG "%s: SanDisk - COR sreset failed (%d)\n",
		       dev->name, res);
		goto done;
	}
	mdelay(5);

	sandisk_set_iobase(local);

	HFA384X_OUTB(0xc5, SANDISK_WLAN_ACTIVATION_OFF);
	udelay(10);
	HFA384X_OUTB(0x4b, SANDISK_WLAN_ACTIVATION_OFF);
	udelay(10);

done:
	return ret;
}


static void prism2_pccard_cor_sreset(local_info_t *local)
{
	int res;
	u8 val;
	struct hostap_cs_priv *hw_priv = local->hw_priv;

	if (!prism2_pccard_card_present(local))
	       return;

	res = pcmcia_read_config_byte(hw_priv->link, CISREG_COR, &val);
	if (res != 0) {
		printk(KERN_DEBUG "prism2_pccard_cor_sreset failed 1 (%d)\n",
		       res);
		return;
	}
	printk(KERN_DEBUG "prism2_pccard_cor_sreset: original COR %02x\n",
		val);

	val |= COR_SOFT_RESET;
	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR, val);
	if (res != 0) {
		printk(KERN_DEBUG "prism2_pccard_cor_sreset failed 2 (%d)\n",
		       res);
		return;
	}

	mdelay(hw_priv->sandisk_connectplus ? 5 : 2);

	val &= ~COR_SOFT_RESET;
	if (hw_priv->sandisk_connectplus)
		val |= COR_IREQ_ENA;
	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR, val);
	if (res != 0) {
		printk(KERN_DEBUG "prism2_pccard_cor_sreset failed 3 (%d)\n",
		       res);
		return;
	}

	mdelay(hw_priv->sandisk_connectplus ? 5 : 2);

	if (hw_priv->sandisk_connectplus)
		sandisk_set_iobase(local);
}


static void prism2_pccard_genesis_reset(local_info_t *local, int hcr)
{
	int res;
	u8 old_cor;
	struct hostap_cs_priv *hw_priv = local->hw_priv;

	if (!prism2_pccard_card_present(local))
	       return;

	if (hw_priv->sandisk_connectplus) {
		sandisk_write_hcr(local, hcr);
		return;
	}

	res = pcmcia_read_config_byte(hw_priv->link, CISREG_COR, &old_cor);
	if (res != 0) {
		printk(KERN_DEBUG "%s failed 1 (%d)\n", __func__, res);
		return;
	}
	printk(KERN_DEBUG "%s: original COR %02x\n", __func__, old_cor);

	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR,
				old_cor | COR_SOFT_RESET);
	if (res != 0) {
		printk(KERN_DEBUG "%s failed 2 (%d)\n", __func__, res);
		return;
	}

	mdelay(10);

	/* Setup Genesis mode */
	res = pcmcia_write_config_byte(hw_priv->link, CISREG_CCSR, hcr);
	if (res != 0) {
		printk(KERN_DEBUG "%s failed 3 (%d)\n", __func__, res);
		return;
	}
	mdelay(10);

	res = pcmcia_write_config_byte(hw_priv->link, CISREG_COR,
				old_cor & ~COR_SOFT_RESET);
	if (res != 0) {
		printk(KERN_DEBUG "%s failed 4 (%d)\n", __func__, res);
		return;
	}

	mdelay(10);
}


static struct prism2_helper_functions prism2_pccard_funcs =
{
	.card_present	= prism2_pccard_card_present,
	.cor_sreset	= prism2_pccard_cor_sreset,
	.genesis_reset	= prism2_pccard_genesis_reset,
	.hw_type	= HOSTAP_HW_PCCARD,
};


/* allocate local data and register with CardServices
 * initialize dev_link structure, but do not configure the card yet */
static int hostap_cs_probe(struct pcmcia_device *p_dev)
{
	int ret;

	PDEBUG(DEBUG_HW, "%s: setting Vcc=33 (constant)\n", dev_info);

	ret = prism2_config(p_dev);
	if (ret) {
		PDEBUG(DEBUG_EXTRA, "prism2_config() failed\n");
	}

	return ret;
}


static void prism2_detach(struct pcmcia_device *link)
{
	PDEBUG(DEBUG_FLOW, "prism2_detach\n");

	prism2_release((u_long)link);

	/* release net devices */
	if (link->priv) {
		struct hostap_cs_priv *hw_priv;
		struct net_device *dev;
		struct hostap_interface *iface;
		dev = link->priv;
		iface = netdev_priv(dev);
		hw_priv = iface->local->hw_priv;
		prism2_free_local_data(dev);
		kfree(hw_priv);
	}
}


static int prism2_config_check(struct pcmcia_device *p_dev, void *priv_data)
{
	if (p_dev->config_index == 0)
		return -EINVAL;

	return pcmcia_request_io(p_dev);
}

static int prism2_config(struct pcmcia_device *link)
{
	struct net_device *dev;
	struct hostap_interface *iface;
	local_info_t *local;
	int ret;
	struct hostap_cs_priv *hw_priv;
	unsigned long flags;

	PDEBUG(DEBUG_FLOW, "prism2_config()\n");

	hw_priv = kzalloc(sizeof(*hw_priv), GFP_KERNEL);
	if (hw_priv == NULL) {
		ret = -ENOMEM;
		goto failed;
	}

	/* Look for an appropriate configuration table entry in the CIS */
	link->config_flags |= CONF_AUTO_SET_VPP | CONF_AUTO_AUDIO |
		CONF_AUTO_CHECK_VCC | CONF_AUTO_SET_IO | CONF_ENABLE_IRQ;
	if (ignore_cis_vcc)
		link->config_flags &= ~CONF_AUTO_CHECK_VCC;
	ret = pcmcia_loop_config(link, prism2_config_check, NULL);
	if (ret) {
		if (!ignore_cis_vcc)
			printk(KERN_ERR "GetNextTuple(): No matching "
			       "CIS configuration.  Maybe you need the "
			       "ignore_cis_vcc=1 parameter.\n");
		goto failed;
	}

	/* Need to allocate net_device before requesting IRQ handler */
	dev = prism2_init_local_data(&prism2_pccard_funcs, 0,
				     &link->dev);
	if (!dev) {
		ret = -ENOMEM;
		goto failed;
	}
	link->priv = dev;

	iface = netdev_priv(dev);
	local = iface->local;
	local->hw_priv = hw_priv;
	hw_priv->link = link;

	/*
	 * We enable IRQ here, but IRQ handler will not proceed
	 * until dev->base_addr is set below. This protect us from
	 * receive interrupts when driver is not initialized.
	 */
	ret = pcmcia_request_irq(link, prism2_interrupt);
	if (ret)
		goto failed;

	ret = pcmcia_enable_device(link);
	if (ret)
		goto failed;

	spin_lock_irqsave(&local->irq_init_lock, flags);
	dev->irq = link->irq;
	dev->base_addr = link->resource[0]->start;
	spin_unlock_irqrestore(&local->irq_init_lock, flags);

	local->shutdown = 0;

	sandisk_enable_wireless(dev);

	ret = prism2_hw_config(dev, 1);
	if (!ret)
		ret = hostap_hw_ready(dev);

	return ret;

 failed:
	kfree(hw_priv);
	prism2_release((u_long)link);
	return ret;
}


static void prism2_release(u_long arg)
{
	struct pcmcia_device *link = (struct pcmcia_device *)arg;

	PDEBUG(DEBUG_FLOW, "prism2_release\n");

	if (link->priv) {
		struct net_device *dev = link->priv;
		struct hostap_interface *iface;

		iface = netdev_priv(dev);
		prism2_hw_shutdown(dev, 0);
		iface->local->shutdown = 1;
	}

	pcmcia_disable_device(link);
	PDEBUG(DEBUG_FLOW, "release - done\n");
}

static int hostap_cs_suspend(struct pcmcia_device *link)
{
	struct net_device *dev = (struct net_device *) link->priv;
	int dev_open = 0;
	struct hostap_interface *iface = NULL;

	if (!dev)
		return -ENODEV;

	iface = netdev_priv(dev);

	PDEBUG(DEBUG_EXTRA, "%s: CS_EVENT_PM_SUSPEND\n", dev_info);
	if (iface && iface->local)
		dev_open = iface->local->num_dev_open > 0;
	if (dev_open) {
		netif_stop_queue(dev);
		netif_device_detach(dev);
	}
	prism2_suspend(dev);

	return 0;
}

static int hostap_cs_resume(struct pcmcia_device *link)
{
	struct net_device *dev = (struct net_device *) link->priv;
	int dev_open = 0;
	struct hostap_interface *iface = NULL;

	if (!dev)
		return -ENODEV;

	iface = netdev_priv(dev);

	PDEBUG(DEBUG_EXTRA, "%s: CS_EVENT_PM_RESUME\n", dev_info);

	if (iface && iface->local)
		dev_open = iface->local->num_dev_open > 0;

	prism2_hw_shutdown(dev, 1);
	prism2_hw_config(dev, dev_open ? 0 : 1);
	if (dev_open) {
		netif_device_attach(dev);
		netif_start_queue(dev);
	}

	return 0;
}

static const struct pcmcia_device_id hostap_cs_ids[] = {
	PCMCIA_DEVICE_MANF_CARD(0x000b, 0x7100),
	PCMCIA_DEVICE_MANF_CARD(0x000b, 0x7300),
	PCMCIA_DEVICE_MANF_CARD(0x0101, 0x0777),
	PCMCIA_DEVICE_MANF_CARD(0x0126, 0x8000),
	PCMCIA_DEVICE_MANF_CARD(0x0138, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0x01bf, 0x3301),
	PCMCIA_DEVICE_MANF_CARD(0x0250, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0x026f, 0x030b),
	PCMCIA_DEVICE_MANF_CARD(0x0274, 0x1612),
	PCMCIA_DEVICE_MANF_CARD(0x0274, 0x1613),
	PCMCIA_DEVICE_MANF_CARD(0x028a, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0x02aa, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0x02d2, 0x0001),
	PCMCIA_DEVICE_MANF_CARD(0x50c2, 0x0001),
	PCMCIA_DEVICE_MANF_CARD(0x50c2, 0x7300),
/*	PCMCIA_DEVICE_MANF_CARD(0xc00f, 0x0000),    conflict with pcnet_cs */
	PCMCIA_DEVICE_MANF_CARD(0xc250, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0xd601, 0x0002),
	PCMCIA_DEVICE_MANF_CARD(0xd601, 0x0005),
	PCMCIA_DEVICE_MANF_CARD(0xd601, 0x0010),
	PCMCIA_DEVICE_MANF_CARD(0x0126, 0x0002),
	PCMCIA_DEVICE_MANF_CARD_PROD_ID1(0xd601, 0x0005, "ADLINK 345 CF",
					 0x2d858104),
	PCMCIA_DEVICE_MANF_CARD_PROD_ID1(0x0156, 0x0002, "INTERSIL",
					 0x74c5e40d),
	PCMCIA_DEVICE_MANF_CARD_PROD_ID1(0x0156, 0x0002, "Intersil",
					 0x4b801a17),
	PCMCIA_DEVICE_MANF_CARD_PROD_ID3(0x0156, 0x0002, "Version 01.02",
					 0x4b74baa0),
	PCMCIA_MFC_DEVICE_PROD_ID12(0, "SanDisk", "ConnectPlus",
				    0x7a954bd9, 0x74be00c6),
	PCMCIA_DEVICE_PROD_ID123(
		"Addtron", "AWP-100 Wireless PCMCIA", "Version 01.02",
		0xe6ec52ce, 0x08649af2, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID123(
		"Canon", "Wireless LAN CF Card K30225", "Version 01.00",
		0x96ef6fe2, 0x263fcbab, 0xa57adb8c),
	PCMCIA_DEVICE_PROD_ID123(
		"D", "Link DWL-650 11Mbps WLAN Card", "Version 01.02",
		0x71b18589, 0xb6f1b0ab, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID123(
		"Instant Wireless ", " Network PC CARD", "Version 01.02",
		0x11d901af, 0x6e9bd926, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID123(
		"SMC", "SMC2632W", "Version 01.02",
		0xc4f8b18b, 0x474a1f2a, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID12("BUFFALO", "WLI-CF-S11G", 
				0x2decece3, 0x82067c18),
	PCMCIA_DEVICE_PROD_ID12("Compaq", "WL200_11Mbps_Wireless_PCI_Card",
				0x54f7c49c, 0x15a75e5b),
	PCMCIA_DEVICE_PROD_ID12("INTERSIL", "HFA384x/IEEE",
				0x74c5e40d, 0xdb472a18),
	PCMCIA_DEVICE_PROD_ID12("Linksys", "Wireless CompactFlash Card",
				0x0733cc81, 0x0c52f395),
	PCMCIA_DEVICE_PROD_ID12(
		"ZoomAir 11Mbps High", "Rate wireless Networking",
		0x273fe3db, 0x32a1eaee),
	PCMCIA_DEVICE_PROD_ID12("NETGEAR MA401 Wireless PC", "Card",
		0xa37434e9, 0x9762e8f1),
	PCMCIA_DEVICE_PROD_ID123(
		"Pretec", "CompactWLAN Card 802.11b", "2.5",
		0x1cadd3e5, 0xe697636c, 0x7a5bfcf1),
	PCMCIA_DEVICE_PROD_ID123(
		"U.S. Robotics", "IEEE 802.11b PC-CARD", "Version 01.02",
		0xc7b8df9d, 0x1700d087, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID123(
		"Allied Telesyn", "AT-WCL452 Wireless PCMCIA Radio",
		"Ver. 1.00",
		0x5cd01705, 0x4271660f, 0x9d08ee12),
	PCMCIA_DEVICE_PROD_ID123(
		"Wireless LAN" , "11Mbps PC Card", "Version 01.02",
		0x4b8870ff, 0x70e946d1, 0x4b74baa0),
	PCMCIA_DEVICE_PROD_ID3("HFA3863", 0x355cb092),
	PCMCIA_DEVICE_PROD_ID3("ISL37100P", 0x630d52b2),
	PCMCIA_DEVICE_PROD_ID3("ISL37101P-10", 0xdd97a26b),
	PCMCIA_DEVICE_PROD_ID3("ISL37300P", 0xc9049a39),
	PCMCIA_DEVICE_NULL
};
MODULE_DEVICE_TABLE(pcmcia, hostap_cs_ids);


static struct pcmcia_driver hostap_driver = {
	.name		= "hostap_cs",
	.probe		= hostap_cs_probe,
	.remove		= prism2_detach,
	.owner		= THIS_MODULE,
	.id_table	= hostap_cs_ids,
	.suspend	= hostap_cs_suspend,
	.resume		= hostap_cs_resume,
};
module_pcmcia_driver(hostap_driver);
="hl opt">(par, BT463_REG_ACC, BT463_BLINK_MASK_0, 0x00); BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_1, 0x00); BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_2, 0x00); BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_3, 0x00); /* Fill the palette. */ BT463_LOAD_ADDR(par, 0x0000); TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); #ifdef CONFIG_HW_CONSOLE for (i = 0; i < 16; i++) { int j = color_table[i]; TGA_WRITE_REG(par, default_red[j], TGA_RAMDAC_REG); TGA_WRITE_REG(par, default_grn[j], TGA_RAMDAC_REG); TGA_WRITE_REG(par, default_blu[j], TGA_RAMDAC_REG); } for (i = 0; i < 512 * 3; i += 4) { #else for (i = 0; i < 528 * 3; i += 4) { #endif TGA_WRITE_REG(par, 0x55, TGA_RAMDAC_REG); TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); } /* Fill window type table after start of vertical retrace. */ while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) continue; TGA_WRITE_REG(par, 0x01, TGA_INTR_STAT_REG); mb(); while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) continue; TGA_WRITE_REG(par, 0x01, TGA_INTR_STAT_REG); BT463_LOAD_ADDR(par, BT463_WINDOW_TYPE_BASE); TGA_WRITE_REG(par, BT463_REG_ACC << 2, TGA_RAMDAC_SETUP_REG); for (i = 0; i < 16; i++) { TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); TGA_WRITE_REG(par, 0x01, TGA_RAMDAC_REG); TGA_WRITE_REG(par, 0x00, TGA_RAMDAC_REG); } } /* Finally, enable video scan (and pray for the monitor... :-) */ TGA_WRITE_REG(par, TGA_VALID_VIDEO, TGA_VALID_REG); return 0; } #define DIFFCHECK(X) \ do { \ if (m <= 0x3f) { \ int delta = f - (TGA_PLL_BASE_FREQ * (X)) / (r << shift); \ if (delta < 0) \ delta = -delta; \ if (delta < min_diff) \ min_diff = delta, vm = m, va = a, vr = r; \ } \ } while (0) static void tgafb_set_pll(struct tga_par *par, int f) { int n, shift, base, min_diff, target; int r,a,m,vm = 34, va = 1, vr = 30; for (r = 0 ; r < 12 ; r++) TGA_WRITE_REG(par, !r, TGA_CLOCK_REG); if (f > TGA_PLL_MAX_FREQ) f = TGA_PLL_MAX_FREQ; if (f >= TGA_PLL_MAX_FREQ / 2) shift = 0; else if (f >= TGA_PLL_MAX_FREQ / 4) shift = 1; else shift = 2; TGA_WRITE_REG(par, shift & 1, TGA_CLOCK_REG); TGA_WRITE_REG(par, shift >> 1, TGA_CLOCK_REG); for (r = 0 ; r < 10 ; r++) TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); if (f <= 120000) { TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); } else if (f <= 200000) { TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); } else { TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); } TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); TGA_WRITE_REG(par, 0, TGA_CLOCK_REG); TGA_WRITE_REG(par, 1, TGA_CLOCK_REG); target = (f << shift) / TGA_PLL_BASE_FREQ; min_diff = TGA_PLL_MAX_FREQ; r = 7 / target; if (!r) r = 1; base = target * r; while (base < 449) { for (n = base < 7 ? 7 : base; n < base + target && n < 449; n++) { m = ((n + 3) / 7) - 1; a = 0; DIFFCHECK((m + 1) * 7); m++; DIFFCHECK((m + 1) * 7); m = (n / 6) - 1; if ((a = n % 6)) DIFFCHECK(n); } r++; base += target; } vr--; for (r = 0; r < 8; r++) TGA_WRITE_REG(par, (vm >> r) & 1, TGA_CLOCK_REG); for (r = 0; r < 8 ; r++) TGA_WRITE_REG(par, (va >> r) & 1, TGA_CLOCK_REG); for (r = 0; r < 7 ; r++) TGA_WRITE_REG(par, (vr >> r) & 1, TGA_CLOCK_REG); TGA_WRITE_REG(par, ((vr >> 7) & 1)|2, TGA_CLOCK_REG); } /** * tgafb_setcolreg - Optional function. Sets a color register. * @regno: boolean, 0 copy local, 1 get_user() function * @red: frame buffer colormap structure * @green: The green value which can be up to 16 bits wide * @blue: The blue value which can be up to 16 bits wide. * @transp: If supported the alpha value which can be up to 16 bits wide. * @info: frame buffer info structure */ static int tgafb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { struct tga_par *par = (struct tga_par *) info->par; int tga_bus_pci = TGA_BUS_PCI(par->dev); int tga_bus_tc = TGA_BUS_TC(par->dev); if (regno > 255) return 1; red >>= 8; green >>= 8; blue >>= 8; if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_pci) { BT485_WRITE(par, regno, BT485_ADDR_PAL_WRITE); TGA_WRITE_REG(par, BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG); TGA_WRITE_REG(par, red|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); TGA_WRITE_REG(par, green|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); TGA_WRITE_REG(par, blue|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); } else if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_tc) { BT459_LOAD_ADDR(par, regno); TGA_WRITE_REG(par, BT459_PALETTE << 2, TGA_RAMDAC_SETUP_REG); TGA_WRITE_REG(par, red, TGA_RAMDAC_REG); TGA_WRITE_REG(par, green, TGA_RAMDAC_REG); TGA_WRITE_REG(par, blue, TGA_RAMDAC_REG); } else { if (regno < 16) { u32 value = (regno << 16) | (regno << 8) | regno; ((u32 *)info->pseudo_palette)[regno] = value; } BT463_LOAD_ADDR(par, regno); TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); TGA_WRITE_REG(par, red, TGA_RAMDAC_REG); TGA_WRITE_REG(par, green, TGA_RAMDAC_REG); TGA_WRITE_REG(par, blue, TGA_RAMDAC_REG); } return 0; } /** * tgafb_blank - Optional function. Blanks the display. * @blank_mode: the blank mode we want. * @info: frame buffer structure that represents a single frame buffer */ static int tgafb_blank(int blank, struct fb_info *info) { struct tga_par *par = (struct tga_par *) info->par; u32 vhcr, vvcr, vvvr; unsigned long flags; local_irq_save(flags); vhcr = TGA_READ_REG(par, TGA_HORIZ_REG); vvcr = TGA_READ_REG(par, TGA_VERT_REG); vvvr = TGA_READ_REG(par, TGA_VALID_REG); vvvr &= ~(TGA_VALID_VIDEO | TGA_VALID_BLANK); switch (blank) { case FB_BLANK_UNBLANK: /* Unblanking */ if (par->vesa_blanked) { TGA_WRITE_REG(par, vhcr & 0xbfffffff, TGA_HORIZ_REG); TGA_WRITE_REG(par, vvcr & 0xbfffffff, TGA_VERT_REG); par->vesa_blanked = 0; } TGA_WRITE_REG(par, vvvr | TGA_VALID_VIDEO, TGA_VALID_REG); break; case FB_BLANK_NORMAL: /* Normal blanking */ TGA_WRITE_REG(par, vvvr | TGA_VALID_VIDEO | TGA_VALID_BLANK, TGA_VALID_REG); break; case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ TGA_WRITE_REG(par, vvcr | 0x40000000, TGA_VERT_REG); TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); par->vesa_blanked = 1; break; case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ TGA_WRITE_REG(par, vhcr | 0x40000000, TGA_HORIZ_REG); TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); par->vesa_blanked = 1; break; case FB_BLANK_POWERDOWN: /* Poweroff */ TGA_WRITE_REG(par, vhcr | 0x40000000, TGA_HORIZ_REG); TGA_WRITE_REG(par, vvcr | 0x40000000, TGA_VERT_REG); TGA_WRITE_REG(par, vvvr | TGA_VALID_BLANK, TGA_VALID_REG); par->vesa_blanked = 1; break; } local_irq_restore(flags); return 0; } /* * Acceleration. */ static void tgafb_mono_imageblit(struct fb_info *info, const struct fb_image *image) { struct tga_par *par = (struct tga_par *) info->par; u32 fgcolor, bgcolor, dx, dy, width, height, vxres, vyres, pixelmask; unsigned long rincr, line_length, shift, pos, is8bpp; unsigned long i, j; const unsigned char *data; void __iomem *regs_base; void __iomem *fb_base; is8bpp = info->var.bits_per_pixel == 8; dx = image->dx; dy = image->dy; width = image->width; height = image->height; vxres = info->var.xres_virtual; vyres = info->var.yres_virtual; line_length = info->fix.line_length; rincr = (width + 7) / 8; /* A shift below cannot cope with. */ if (unlikely(width == 0)) return; /* Crop the image to the screen. */ if (dx > vxres || dy > vyres) return; if (dx + width > vxres) width = vxres - dx; if (dy + height > vyres) height = vyres - dy; regs_base = par->tga_regs_base; fb_base = par->tga_fb_base; /* Expand the color values to fill 32-bits. */ /* ??? Would be nice to notice colour changes elsewhere, so that we can do this only when necessary. */ fgcolor = image->fg_color; bgcolor = image->bg_color; if (is8bpp) { fgcolor |= fgcolor << 8; fgcolor |= fgcolor << 16; bgcolor |= bgcolor << 8; bgcolor |= bgcolor << 16; } else { if (fgcolor < 16) fgcolor = ((u32 *)info->pseudo_palette)[fgcolor]; if (bgcolor < 16) bgcolor = ((u32 *)info->pseudo_palette)[bgcolor]; } __raw_writel(fgcolor, regs_base + TGA_FOREGROUND_REG); __raw_writel(bgcolor, regs_base + TGA_BACKGROUND_REG); /* Acquire proper alignment; set up the PIXELMASK register so that we only write the proper character cell. */ pos = dy * line_length; if (is8bpp) { pos += dx; shift = pos & 3; pos &= -4; } else { pos += dx * 4; shift = (pos & 7) >> 2; pos &= -8; } data = (const unsigned char *) image->data; /* Enable opaque stipple mode. */ __raw_writel((is8bpp ? TGA_MODE_SBM_8BPP | TGA_MODE_OPAQUE_STIPPLE : TGA_MODE_SBM_24BPP | TGA_MODE_OPAQUE_STIPPLE), regs_base + TGA_MODE_REG); if (width + shift <= 32) { unsigned long bwidth; /* Handle common case of imaging a single character, in a font less than or 32 pixels wide. */ /* Avoid a shift by 32; width > 0 implied. */ pixelmask = (2ul << (width - 1)) - 1; pixelmask <<= shift; __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); wmb(); bwidth = (width + 7) / 8; for (i = 0; i < height; ++i) { u32 mask = 0; /* The image data is bit big endian; we need little endian. */ for (j = 0; j < bwidth; ++j) mask |= bitrev8(data[j]) << (j * 8); __raw_writel(mask << shift, fb_base + pos); pos += line_length; data += rincr; } wmb(); __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); } else if (shift == 0) { unsigned long pos0 = pos; const unsigned char *data0 = data; unsigned long bincr = (is8bpp ? 8 : 8*4); unsigned long bwidth; /* Handle another common case in which accel_putcs generates a large bitmap, which happens to be aligned. Allow the tail to be misaligned. This case is interesting because we've not got to hold partial bytes across the words being written. */ wmb(); bwidth = (width / 8) & -4; for (i = 0; i < height; ++i) { for (j = 0; j < bwidth; j += 4) { u32 mask = 0; mask |= bitrev8(data[j+0]) << (0 * 8); mask |= bitrev8(data[j+1]) << (1 * 8); mask |= bitrev8(data[j+2]) << (2 * 8); mask |= bitrev8(data[j+3]) << (3 * 8); __raw_writel(mask, fb_base + pos + j*bincr); } pos += line_length; data += rincr; } wmb(); pixelmask = (1ul << (width & 31)) - 1; if (pixelmask) { __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); wmb(); pos = pos0 + bwidth*bincr; data = data0 + bwidth; bwidth = ((width & 31) + 7) / 8; for (i = 0; i < height; ++i) { u32 mask = 0; for (j = 0; j < bwidth; ++j) mask |= bitrev8(data[j]) << (j * 8); __raw_writel(mask, fb_base + pos); pos += line_length; data += rincr; } wmb(); __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); } } else { unsigned long pos0 = pos; const unsigned char *data0 = data; unsigned long bincr = (is8bpp ? 8 : 8*4); unsigned long bwidth; /* Finally, handle the generic case of misaligned start. Here we split the write into 16-bit spans. This allows us to use only one pixel mask, instead of four as would be required by writing 24-bit spans. */ pixelmask = 0xffff << shift; __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); wmb(); bwidth = (width / 8) & -2; for (i = 0; i < height; ++i) { for (j = 0; j < bwidth; j += 2) { u32 mask = 0; mask |= bitrev8(data[j+0]) << (0 * 8); mask |= bitrev8(data[j+1]) << (1 * 8); mask <<= shift; __raw_writel(mask, fb_base + pos + j*bincr); } pos += line_length; data += rincr; } wmb(); pixelmask = ((1ul << (width & 15)) - 1) << shift; if (pixelmask) { __raw_writel(pixelmask, regs_base + TGA_PIXELMASK_REG); wmb(); pos = pos0 + bwidth*bincr; data = data0 + bwidth; bwidth = (width & 15) > 8; for (i = 0; i < height; ++i) { u32 mask = bitrev8(data[0]); if (bwidth) mask |= bitrev8(data[1]) << 8; mask <<= shift; __raw_writel(mask, fb_base + pos); pos += line_length; data += rincr; } wmb(); } __raw_writel(0xffffffff, regs_base + TGA_PIXELMASK_REG); } /* Disable opaque stipple mode. */ __raw_writel((is8bpp ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), regs_base + TGA_MODE_REG); } static void tgafb_clut_imageblit(struct fb_info *info, const struct fb_image *image) { struct tga_par *par = (struct tga_par *) info->par; u32 color, dx, dy, width, height, vxres, vyres; u32 *palette = ((u32 *)info->pseudo_palette); unsigned long pos, line_length, i, j; const unsigned char *data; void __iomem *regs_base, *fb_base; dx = image->dx; dy = image->dy; width = image->width; height = image->height; vxres = info->var.xres_virtual; vyres = info->var.yres_virtual; line_length = info->fix.line_length; /* Crop the image to the screen. */ if (dx > vxres || dy > vyres) return; if (dx + width > vxres) width = vxres - dx; if (dy + height > vyres) height = vyres - dy; regs_base = par->tga_regs_base; fb_base = par->tga_fb_base; pos = dy * line_length + (dx * 4); data = image->data; /* Now copy the image, color_expanding via the palette. */ for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { color = palette[*data++]; __raw_writel(color, fb_base + pos + j*4); } pos += line_length; } } /** * tgafb_imageblit - REQUIRED function. Can use generic routines if * non acclerated hardware and packed pixel based. * Copies a image from system memory to the screen. * * @info: frame buffer structure that represents a single frame buffer * @image: structure defining the image. */ static void tgafb_imageblit(struct fb_info *info, const struct fb_image *image) { unsigned int is8bpp = info->var.bits_per_pixel == 8; /* If a mono image, regardless of FB depth, go do it. */ if (image->depth == 1) { tgafb_mono_imageblit(info, image); return; } /* For copies that aren't pixel expansion, there's little we can do better than the generic code. */ /* ??? There is a DMA write mode; I wonder if that could be made to pull the data from the image buffer... */ if (image->depth == info->var.bits_per_pixel) { cfb_imageblit(info, image); return; } /* If 24-plane FB and the image is 8-plane with CLUT, we can do it. */ if (!is8bpp && image->depth == 8) { tgafb_clut_imageblit(info, image); return; } /* Silently return... */ } /** * tgafb_fillrect - REQUIRED function. Can use generic routines if * non acclerated hardware and packed pixel based. * Draws a rectangle on the screen. * * @info: frame buffer structure that represents a single frame buffer * @rect: structure defining the rectagle and operation. */ static void tgafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct tga_par *par = (struct tga_par *) info->par; int is8bpp = info->var.bits_per_pixel == 8; u32 dx, dy, width, height, vxres, vyres, color; unsigned long pos, align, line_length, i, j; void __iomem *regs_base; void __iomem *fb_base; dx = rect->dx; dy = rect->dy; width = rect->width; height = rect->height; vxres = info->var.xres_virtual; vyres = info->var.yres_virtual; line_length = info->fix.line_length; regs_base = par->tga_regs_base; fb_base = par->tga_fb_base; /* Crop the rectangle to the screen. */ if (dx > vxres || dy > vyres || !width || !height) return; if (dx + width > vxres) width = vxres - dx; if (dy + height > vyres) height = vyres - dy; pos = dy * line_length + dx * (is8bpp ? 1 : 4); /* ??? We could implement ROP_XOR with opaque fill mode and a RasterOp setting of GXxor, but as far as I can tell, this mode is not actually used in the kernel. Thus I am ignoring it for now. */ if (rect->rop != ROP_COPY) { cfb_fillrect(info, rect); return; } /* Expand the color value to fill 8 pixels. */ color = rect->color; if (is8bpp) { color |= color << 8; color |= color << 16; __raw_writel(color, regs_base + TGA_BLOCK_COLOR0_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR1_REG); } else { if (color < 16) color = ((u32 *)info->pseudo_palette)[color]; __raw_writel(color, regs_base + TGA_BLOCK_COLOR0_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR1_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR2_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR3_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR4_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR5_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR6_REG); __raw_writel(color, regs_base + TGA_BLOCK_COLOR7_REG); } /* The DATA register holds the fill mask for block fill mode. Since we're not stippling, this is all ones. */ __raw_writel(0xffffffff, regs_base + TGA_DATA_REG); /* Enable block fill mode. */ __raw_writel((is8bpp ? TGA_MODE_SBM_8BPP | TGA_MODE_BLOCK_FILL : TGA_MODE_SBM_24BPP | TGA_MODE_BLOCK_FILL), regs_base + TGA_MODE_REG); wmb(); /* We can fill 2k pixels per operation. Notice blocks that fit the width of the screen so that we can take advantage of this and fill more than one line per write. */ if (width == line_length) width *= height, height = 1; /* The write into the frame buffer must be aligned to 4 bytes, but we are allowed to encode the offset within the word in the data word written. */ align = (pos & 3) << 16; pos &= -4; if (width <= 2048) { u32 data; data = (width - 1) | align; for (i = 0; i < height; ++i) { __raw_writel(data, fb_base + pos); pos += line_length; } } else { unsigned long Bpp = (is8bpp ? 1 : 4); unsigned long nwidth = width & -2048; u32 fdata, ldata; fdata = (2048 - 1) | align; ldata = ((width & 2047) - 1) | align; for (i = 0; i < height; ++i) { for (j = 0; j < nwidth; j += 2048) __raw_writel(fdata, fb_base + pos + j*Bpp); if (j < width) __raw_writel(ldata, fb_base + pos + j*Bpp); pos += line_length; } } wmb(); /* Disable block fill mode. */ __raw_writel((is8bpp ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), regs_base + TGA_MODE_REG); } /** * tgafb_copyarea - REQUIRED function. Can use generic routines if * non acclerated hardware and packed pixel based. * Copies on area of the screen to another area. * * @info: frame buffer structure that represents a single frame buffer * @area: structure defining the source and destination. */ /* Handle the special case of copying entire lines, e.g. during scrolling. We can avoid a lot of needless computation in this case. In the 8bpp case we need to use the COPY64 registers instead of mask writes into the frame buffer to achieve maximum performance. */ static inline void copyarea_line_8bpp(struct fb_info *info, u32 dy, u32 sy, u32 height, u32 width) { struct tga_par *par = (struct tga_par *) info->par; void __iomem *tga_regs = par->tga_regs_base; unsigned long dpos, spos, i, n64; /* Set up the MODE and PIXELSHIFT registers. */ __raw_writel(TGA_MODE_SBM_8BPP | TGA_MODE_COPY, tga_regs+TGA_MODE_REG); __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); wmb(); n64 = (height * width) / 64; if (sy < dy) { spos = (sy + height) * width; dpos = (dy + height) * width; for (i = 0; i < n64; ++i) { spos -= 64; dpos -= 64; __raw_writel(spos, tga_regs+TGA_COPY64_SRC); wmb(); __raw_writel(dpos, tga_regs+TGA_COPY64_DST); wmb(); } } else { spos = sy * width; dpos = dy * width; for (i = 0; i < n64; ++i) { __raw_writel(spos, tga_regs+TGA_COPY64_SRC); wmb(); __raw_writel(dpos, tga_regs+TGA_COPY64_DST); wmb(); spos += 64; dpos += 64; } } /* Reset the MODE register to normal. */ __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); } static inline void copyarea_line_32bpp(struct fb_info *info, u32 dy, u32 sy, u32 height, u32 width) { struct tga_par *par = (struct tga_par *) info->par; void __iomem *tga_regs = par->tga_regs_base; void __iomem *tga_fb = par->tga_fb_base; void __iomem *src; void __iomem *dst; unsigned long i, n16; /* Set up the MODE and PIXELSHIFT registers. */ __raw_writel(TGA_MODE_SBM_24BPP | TGA_MODE_COPY, tga_regs+TGA_MODE_REG); __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); wmb(); n16 = (height * width) / 16; if (sy < dy) { src = tga_fb + (sy + height) * width * 4; dst = tga_fb + (dy + height) * width * 4; for (i = 0; i < n16; ++i) { src -= 64; dst -= 64; __raw_writel(0xffff, src); wmb(); __raw_writel(0xffff, dst); wmb(); } } else { src = tga_fb + sy * width * 4; dst = tga_fb + dy * width * 4; for (i = 0; i < n16; ++i) { __raw_writel(0xffff, src); wmb(); __raw_writel(0xffff, dst); wmb(); src += 64; dst += 64; } } /* Reset the MODE register to normal. */ __raw_writel(TGA_MODE_SBM_24BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); } /* The general case of forward copy in 8bpp mode. */ static inline void copyarea_foreward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy, u32 height, u32 width, u32 line_length) { struct tga_par *par = (struct tga_par *) info->par; unsigned long i, copied, left; unsigned long dpos, spos, dalign, salign, yincr; u32 smask_first, dmask_first, dmask_last; int pixel_shift, need_prime, need_second; unsigned long n64, n32, xincr_first; void __iomem *tga_regs; void __iomem *tga_fb; yincr = line_length; if (dy > sy) { dy += height - 1; sy += height - 1; yincr = -yincr; } /* Compute the offsets and alignments in the frame buffer. More than anything else, these control how we do copies. */ dpos = dy * line_length + dx; spos = sy * line_length + sx; dalign = dpos & 7; salign = spos & 7; dpos &= -8; spos &= -8; /* Compute the value for the PIXELSHIFT register. This controls both non-co-aligned source and destination and copy direction. */ if (dalign >= salign) pixel_shift = dalign - salign; else pixel_shift = 8 - (salign - dalign); /* Figure out if we need an additional priming step for the residue register. */ need_prime = (salign > dalign); if (need_prime) dpos -= 8; /* Begin by copying the leading unaligned destination. Copy enough to make the next destination address 32-byte aligned. */ copied = 32 - (dalign + (dpos & 31)); if (copied == 32) copied = 0; xincr_first = (copied + 7) & -8; smask_first = dmask_first = (1ul << copied) - 1; smask_first <<= salign; dmask_first <<= dalign + need_prime*8; if (need_prime && copied > 24) copied -= 8; left = width - copied; /* Care for small copies. */ if (copied > width) { u32 t; t = (1ul << width) - 1; t <<= dalign + need_prime*8; dmask_first &= t; left = 0; } /* Attempt to use 64-byte copies. This is only possible if the source and destination are co-aligned at 64 bytes. */ n64 = need_second = 0; if ((dpos & 63) == (spos & 63) && (height == 1 || line_length % 64 == 0)) { /* We may need a 32-byte copy to ensure 64 byte alignment. */ need_second = (dpos + xincr_first) & 63; if ((need_second & 32) != need_second) printk(KERN_ERR "tgafb: need_second wrong\n"); if (left >= need_second + 64) { left -= need_second; n64 = left / 64; left %= 64; } else need_second = 0; } /* Copy trailing full 32-byte sections. This will be the main loop if the 64 byte loop can't be used. */ n32 = left / 32; left %= 32; /* Copy the trailing unaligned destination. */ dmask_last = (1ul << left) - 1; tga_regs = par->tga_regs_base; tga_fb = par->tga_fb_base; /* Set up the MODE and PIXELSHIFT registers. */ __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, tga_regs+TGA_MODE_REG); __raw_writel(pixel_shift, tga_regs+TGA_PIXELSHIFT_REG); wmb(); for (i = 0; i < height; ++i) { unsigned long j; void __iomem *sfb; void __iomem *dfb; sfb = tga_fb + spos; dfb = tga_fb + dpos; if (dmask_first) { __raw_writel(smask_first, sfb); wmb(); __raw_writel(dmask_first, dfb); wmb(); sfb += xincr_first; dfb += xincr_first; } if (need_second) { __raw_writel(0xffffffff, sfb); wmb(); __raw_writel(0xffffffff, dfb); wmb(); sfb += 32; dfb += 32; } if (n64 && (((unsigned long)sfb | (unsigned long)dfb) & 63)) printk(KERN_ERR "tgafb: misaligned copy64 (s:%p, d:%p)\n", sfb, dfb); for (j = 0; j < n64; ++j) { __raw_writel(sfb - tga_fb, tga_regs+TGA_COPY64_SRC); wmb(); __raw_writel(dfb - tga_fb, tga_regs+TGA_COPY64_DST); wmb(); sfb += 64; dfb += 64; } for (j = 0; j < n32; ++j) { __raw_writel(0xffffffff, sfb); wmb(); __raw_writel(0xffffffff, dfb); wmb(); sfb += 32; dfb += 32; } if (dmask_last) { __raw_writel(0xffffffff, sfb); wmb(); __raw_writel(dmask_last, dfb); wmb(); } spos += yincr; dpos += yincr; } /* Reset the MODE register to normal. */ __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); } /* The (almost) general case of backward copy in 8bpp mode. */ static inline void copyarea_backward_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy, u32 height, u32 width, u32 line_length, const struct fb_copyarea *area) { struct tga_par *par = (struct tga_par *) info->par; unsigned long i, left, yincr; unsigned long depos, sepos, dealign, sealign; u32 mask_first, mask_last; unsigned long n32; void __iomem *tga_regs; void __iomem *tga_fb; yincr = line_length; if (dy > sy) { dy += height - 1; sy += height - 1; yincr = -yincr; } /* Compute the offsets and alignments in the frame buffer. More than anything else, these control how we do copies. */ depos = dy * line_length + dx + width; sepos = sy * line_length + sx + width; dealign = depos & 7; sealign = sepos & 7; /* ??? The documentation appears to be incorrect (or very misleading) wrt how pixel shifting works in backward copy mode, i.e. when PIXELSHIFT is negative. I give up for now. Do handle the common case of co-aligned backward copies, but frob everything else back on generic code. */ if (dealign != sealign) { cfb_copyarea(info, area); return; } /* We begin the copy with the trailing pixels of the unaligned destination. */ mask_first = (1ul << dealign) - 1; left = width - dealign; /* Care for small copies. */ if (dealign > width) { mask_first ^= (1ul << (dealign - width)) - 1; left = 0; } /* Next copy full words at a time. */ n32 = left / 32; left %= 32; /* Finally copy the unaligned head of the span. */ mask_last = -1 << (32 - left); tga_regs = par->tga_regs_base; tga_fb = par->tga_fb_base; /* Set up the MODE and PIXELSHIFT registers. */ __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, tga_regs+TGA_MODE_REG); __raw_writel(0, tga_regs+TGA_PIXELSHIFT_REG); wmb(); for (i = 0; i < height; ++i) { unsigned long j; void __iomem *sfb; void __iomem *dfb; sfb = tga_fb + sepos; dfb = tga_fb + depos; if (mask_first) { __raw_writel(mask_first, sfb); wmb(); __raw_writel(mask_first, dfb); wmb(); } for (j = 0; j < n32; ++j) { sfb -= 32; dfb -= 32; __raw_writel(0xffffffff, sfb); wmb(); __raw_writel(0xffffffff, dfb); wmb(); } if (mask_last) { sfb -= 32; dfb -= 32; __raw_writel(mask_last, sfb); wmb(); __raw_writel(mask_last, dfb); wmb(); } sepos += yincr; depos += yincr; } /* Reset the MODE register to normal. */ __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, tga_regs+TGA_MODE_REG); } static void tgafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) { unsigned long dx, dy, width, height, sx, sy, vxres, vyres; unsigned long line_length, bpp; dx = area->dx; dy = area->dy; width = area->width; height = area->height; sx = area->sx; sy = area->sy; vxres = info->var.xres_virtual; vyres = info->var.yres_virtual; line_length = info->fix.line_length; /* The top left corners must be in the virtual screen. */ if (dx > vxres || sx > vxres || dy > vyres || sy > vyres) return; /* Clip the destination. */ if (dx + width > vxres) width = vxres - dx; if (dy + height > vyres) height = vyres - dy; /* The source must be completely inside the virtual screen. */ if (sx + width > vxres || sy + height > vyres) return; bpp = info->var.bits_per_pixel; /* Detect copies of the entire line. */ if (width * (bpp >> 3) == line_length) { if (bpp == 8) copyarea_line_8bpp(info, dy, sy, height, width); else copyarea_line_32bpp(info, dy, sy, height, width); } /* ??? The documentation is unclear to me exactly how the pixelshift register works in 32bpp mode. Since I don't have hardware to test, give up for now and fall back on the generic routines. */ else if (bpp == 32) cfb_copyarea(info, area); /* Detect overlapping source and destination that requires a backward copy. */ else if (dy == sy && dx > sx && dx < sx + width) copyarea_backward_8bpp(info, dx, dy, sx, sy, height, width, line_length, area); else copyarea_foreward_8bpp(info, dx, dy, sx, sy, height, width, line_length); } /* * Initialisation */ static void tgafb_init_fix(struct fb_info *info) { struct tga_par *par = (struct tga_par *)info->par; int tga_bus_pci = TGA_BUS_PCI(par->dev); int tga_bus_tc = TGA_BUS_TC(par->dev); u8 tga_type = par->tga_type; const char *tga_type_name = NULL; switch (tga_type) { case TGA_TYPE_8PLANE: if (tga_bus_pci) tga_type_name = "Digital ZLXp-E1"; if (tga_bus_tc) tga_type_name = "Digital ZLX-E1"; break; case TGA_TYPE_24PLANE: if (tga_bus_pci) tga_type_name = "Digital ZLXp-E2"; if (tga_bus_tc) tga_type_name = "Digital ZLX-E2"; break; case TGA_TYPE_24PLUSZ: if (tga_bus_pci) tga_type_name = "Digital ZLXp-E3"; if (tga_bus_tc) tga_type_name = "Digital ZLX-E3"; break; default: tga_type_name = "Unknown"; break; } strlcpy(info->fix.id, tga_type_name, sizeof(info->fix.id)); info->fix.type = FB_TYPE_PACKED_PIXELS; info->fix.type_aux = 0; info->fix.visual = (tga_type == TGA_TYPE_8PLANE ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_DIRECTCOLOR); info->fix.line_length = par->xres * (par->bits_per_pixel >> 3); info->fix.smem_start = (size_t) par->tga_fb_base; info->fix.smem_len = info->fix.line_length * par->yres; info->fix.mmio_start = (size_t) par->tga_regs_base; info->fix.mmio_len = 512; info->fix.xpanstep = 0; info->fix.ypanstep = 0; info->fix.ywrapstep = 0; info->fix.accel = FB_ACCEL_DEC_TGA; /* * These are needed by fb_set_logo_truepalette(), so we * set them here for 24-plane cards. */ if (tga_type != TGA_TYPE_8PLANE) { info->var.red.length = 8; info->var.green.length = 8; info->var.blue.length = 8; info->var.red.offset = 16; info->var.green.offset = 8; info->var.blue.offset = 0; } } static int tgafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { /* We just use this to catch switches out of graphics mode. */ tgafb_set_par(info); /* A bit of overkill for BASE_ADDR reset. */ return 0; } static int tgafb_register(struct device *dev) { static const struct fb_videomode modedb_tc = { /* 1280x1024 @ 72 Hz, 76.8 kHz hsync */ "1280x1024@72", 0, 1280, 1024, 7645, 224, 28, 33, 3, 160, 3, FB_SYNC_ON_GREEN, FB_VMODE_NONINTERLACED }; static unsigned int const fb_offset_presets[4] = { TGA_8PLANE_FB_OFFSET, TGA_24PLANE_FB_OFFSET, 0xffffffff, TGA_24PLUSZ_FB_OFFSET }; const struct fb_videomode *modedb_tga = NULL; resource_size_t bar0_start = 0, bar0_len = 0; const char *mode_option_tga = NULL; int tga_bus_pci = TGA_BUS_PCI(dev); int tga_bus_tc = TGA_BUS_TC(dev); unsigned int modedbsize_tga = 0; void __iomem *mem_base; struct fb_info *info; struct tga_par *par; u8 tga_type; int ret = 0; /* Enable device in PCI config. */ if (tga_bus_pci && pci_enable_device(to_pci_dev(dev))) { printk(KERN_ERR "tgafb: Cannot enable PCI device\n"); return -ENODEV; } /* Allocate the fb and par structures. */ info = framebuffer_alloc(sizeof(struct tga_par), dev); if (!info) { printk(KERN_ERR "tgafb: Cannot allocate memory\n"); return -ENOMEM; } par = info->par; dev_set_drvdata(dev, info); /* Request the mem regions. */ ret = -ENODEV; if (tga_bus_pci) { bar0_start = pci_resource_start(to_pci_dev(dev), 0); bar0_len = pci_resource_len(to_pci_dev(dev), 0); } if (tga_bus_tc) { bar0_start = to_tc_dev(dev)->resource.start; bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; } if (!request_mem_region (bar0_start, bar0_len, "tgafb")) { printk(KERN_ERR "tgafb: cannot reserve FB region\n"); goto err0; } /* Map the framebuffer. */ mem_base = ioremap_nocache(bar0_start, bar0_len); if (!mem_base) { printk(KERN_ERR "tgafb: Cannot map MMIO\n"); goto err1; } /* Grab info about the card. */ tga_type = (readl(mem_base) >> 12) & 0x0f; par->dev = dev; par->tga_mem_base = mem_base; par->tga_fb_base = mem_base + fb_offset_presets[tga_type]; par->tga_regs_base = mem_base + TGA_REGS_OFFSET; par->tga_type = tga_type; if (tga_bus_pci) par->tga_chip_rev = (to_pci_dev(dev))->revision; if (tga_bus_tc) par->tga_chip_rev = TGA_READ_REG(par, TGA_START_REG) & 0xff; /* Setup framebuffer. */ info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT; info->fbops = &tgafb_ops; info->screen_base = par->tga_fb_base; info->pseudo_palette = par->palette; /* This should give a reasonable default video mode. */ if (tga_bus_pci) { mode_option_tga = mode_option_pci; } if (tga_bus_tc) { mode_option_tga = mode_option_tc; modedb_tga = &modedb_tc; modedbsize_tga = 1; } ret = fb_find_mode(&info->var, info, mode_option ? mode_option : mode_option_tga, modedb_tga, modedbsize_tga, NULL, tga_type == TGA_TYPE_8PLANE ? 8 : 32); if (ret == 0 || ret == 4) { printk(KERN_ERR "tgafb: Could not find valid video mode\n"); ret = -EINVAL; goto err1; } if (fb_alloc_cmap(&info->cmap, 256, 0)) { printk(KERN_ERR "tgafb: Could not allocate color map\n"); ret = -ENOMEM; goto err1; } tgafb_set_par(info); tgafb_init_fix(info); if (register_framebuffer(info) < 0) { printk(KERN_ERR "tgafb: Could not register framebuffer\n"); ret = -EINVAL; goto err2; } if (tga_bus_pci) { pr_info("tgafb: DC21030 [TGA] detected, rev=0x%02x\n", par->tga_chip_rev); pr_info("tgafb: at PCI bus %d, device %d, function %d\n", to_pci_dev(dev)->bus->number, PCI_SLOT(to_pci_dev(dev)->devfn), PCI_FUNC(to_pci_dev(dev)->devfn)); } if (tga_bus_tc) pr_info("tgafb: SFB+ detected, rev=0x%02x\n", par->tga_chip_rev); pr_info("fb%d: %s frame buffer device at 0x%lx\n", info->node, info->fix.id, (long)bar0_start); return 0; err2: fb_dealloc_cmap(&info->cmap); err1: if (mem_base) iounmap(mem_base); release_mem_region(bar0_start, bar0_len); err0: framebuffer_release(info); return ret; } static void tgafb_unregister(struct device *dev) { resource_size_t bar0_start = 0, bar0_len = 0; int tga_bus_pci = TGA_BUS_PCI(dev); int tga_bus_tc = TGA_BUS_TC(dev); struct fb_info *info = NULL; struct tga_par *par; info = dev_get_drvdata(dev); if (!info) return; par = info->par; unregister_framebuffer(info); fb_dealloc_cmap(&info->cmap); iounmap(par->tga_mem_base); if (tga_bus_pci) { bar0_start = pci_resource_start(to_pci_dev(dev), 0); bar0_len = pci_resource_len(to_pci_dev(dev), 0); } if (tga_bus_tc) { bar0_start = to_tc_dev(dev)->resource.start; bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; } release_mem_region(bar0_start, bar0_len); framebuffer_release(info); } static void tgafb_exit(void) { tc_unregister_driver(&tgafb_tc_driver); pci_unregister_driver(&tgafb_pci_driver); } #ifndef MODULE static int tgafb_setup(char *arg) { char *this_opt; if (arg && *arg) { while ((this_opt = strsep(&arg, ","))) { if (!*this_opt) continue; if (!strncmp(this_opt, "mode:", 5))