1/*
2 * Hardware driver for the MixCom synchronous serial board
3 *
4 * Author: Gergely Madarasz <gorgo@itc.hu>
5 *
6 * based on skeleton driver code and a preliminary hscx driver by
7 * Tivadar Szemethy <tiv@itc.hu>
8 *
9 * Copyright (C) 1998-1999 ITConsult-Pro Co. <info@itc.hu>
10 *
11 * Contributors:
12 * Arnaldo Carvalho de Melo <acme@conectiva.com.br> (0.65)
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version
17 * 2 of the License, or (at your option) any later version.
18 *
19 * Version 0.60 (99/06/11):
20 *		- ported to the kernel, now works as builtin code
21 *
22 * Version 0.61 (99/06/11):
23 *		- recognize the one-channel MixCOM card (id byte = 0x13)
24 *		- printk fixes
25 *
26 * Version 0.62 (99/07/15):
27 *		- fixes according to the new hw docs
28 *		- report line status when open
29 *
30 * Version 0.63 (99/09/21):
31 *		- line status report fixes
32 *
33 * Version 0.64 (99/12/01):
34 *		- some more cosmetical fixes
35 *
36 * Version 0.65 (00/08/15)
37 *		- resource release on failure at MIXCOM_init
38 */
39
40#define VERSION "0.65"
41
42#include <linux/module.h>
43#include <linux/version.h>
44#include <linux/types.h>
45#include <linux/sched.h>
46#include <linux/netdevice.h>
47#include <linux/proc_fs.h>
48#include <asm/types.h>
49#include <asm/uaccess.h>
50#include <asm/io.h>
51#include <linux/ioport.h>
52#include <linux/delay.h>
53#include <linux/init.h>
54
55#include "comx.h"
56#include "mixcom.h"
57#include "hscx.h"
58
59MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
60MODULE_DESCRIPTION("Hardware-level driver for the serial port of the MixCom board");
61MODULE_LICENSE("GPL");
62
63#define MIXCOM_DATA(d) ((struct mixcom_privdata *)(COMX_CHANNEL(d)-> \
64	HW_privdata))
65
66#define MIXCOM_BOARD_BASE(d) (d->base_addr - MIXCOM_SERIAL_OFFSET - \
67	(1 - MIXCOM_DATA(d)->channel) * MIXCOM_CHANNEL_OFFSET)
68
69#define MIXCOM_DEV_BASE(port,channel) (port + MIXCOM_SERIAL_OFFSET + \
70	(1 - channel) * MIXCOM_CHANNEL_OFFSET)
71
72/* Values used to set the IRQ line */
73static unsigned char mixcom_set_irq[]={0xFF, 0xFF, 0xFF, 0x0, 0xFF, 0x2, 0x4, 0x6, 0xFF, 0xFF, 0x8, 0xA, 0xC, 0xFF, 0xE, 0xFF};
74
75static unsigned char* hscx_versions[]={"A1", NULL, "A2", NULL, "A3", "2.1"};
76
77struct mixcom_privdata {
78	u16	clock;
79	char	channel;
80	long	txbusy;
81	struct sk_buff *sending;
82	unsigned tx_ptr;
83	struct sk_buff *recving;
84	unsigned rx_ptr;
85	unsigned char status;
86	char	card_has_status;
87};
88
89static inline void wr_hscx(struct net_device *dev, int reg, unsigned char val)
90{
91	outb(val, dev->base_addr + reg);
92}
93
94static inline unsigned char rd_hscx(struct net_device *dev, int reg)
95{
96	return inb(dev->base_addr + reg);
97}
98
99static inline void hscx_cmd(struct net_device *dev, int cmd)
100{
101	unsigned long jiffs = jiffies;
102	unsigned char cec;
103	unsigned delay = 0;
104
105	while ((cec = (rd_hscx(dev, HSCX_STAR) & HSCX_CEC) != 0) &&
106	    (jiffs + HZ > jiffies)) {
107		udelay(1);
108		if (++delay > (100000 / HZ)) break;
109	}
110	if (cec) {
111		printk(KERN_WARNING "%s: CEC stuck, probably no clock!\n",dev->name);
112	} else {
113		wr_hscx(dev, HSCX_CMDR, cmd);
114	}
115}
116
117static inline void hscx_fill_fifo(struct net_device *dev)
118{
119	struct comx_channel *ch = dev->priv;
120	struct mixcom_privdata *hw = ch->HW_privdata;
121	register word to_send = hw->sending->len - hw->tx_ptr;
122
123
124	outsb(dev->base_addr + HSCX_FIFO,
125        	&(hw->sending->data[hw->tx_ptr]), min_t(unsigned int, to_send, 32));
126	if (to_send <= 32) {
127        	hscx_cmd(dev, HSCX_XTF | HSCX_XME);
128	        kfree_skb(hw->sending);
129        	hw->sending = NULL;
130        	hw->tx_ptr = 0;
131        } else {
132	        hscx_cmd(dev, HSCX_XTF);
133        	hw->tx_ptr += 32;
134        }
135}
136
137static inline void hscx_empty_fifo(struct net_device *dev, int cnt)
138{
139	struct comx_channel *ch = dev->priv;
140	struct mixcom_privdata *hw = ch->HW_privdata;
141
142	if (hw->recving == NULL) {
143        	if (!(hw->recving = dev_alloc_skb(HSCX_MTU + 16))) {
144	                ch->stats.rx_dropped++;
145        	        hscx_cmd(dev, HSCX_RHR);
146                } else {
147	                skb_reserve(hw->recving, 16);
148        	        skb_put(hw->recving, HSCX_MTU);
149                }
150	        hw->rx_ptr = 0;
151        }
152	if (cnt > 32 || !cnt || hw->recving == NULL) {
153        	printk(KERN_ERR "hscx_empty_fifo: cnt is %d, hw->recving %p\n",
154		        cnt, (void *)hw->recving);
155	        return;
156        }
157
158	insb(dev->base_addr + HSCX_FIFO, &(hw->recving->data[hw->rx_ptr]),cnt);
159	hw->rx_ptr += cnt;
160	hscx_cmd(dev, HSCX_RMC);
161}
162
163
164static int MIXCOM_txe(struct net_device *dev)
165{
166	struct comx_channel *ch = dev->priv;
167	struct mixcom_privdata *hw = ch->HW_privdata;
168
169	return !test_bit(0, &hw->txbusy);
170}
171
172static int mixcom_probe(struct net_device *dev)
173{
174	unsigned long flags;
175	int id, vstr, ret=0;
176
177	save_flags(flags); cli();
178
179	id=inb_p(MIXCOM_BOARD_BASE(dev) + MIXCOM_ID_OFFSET) & 0x7f;
180
181 	if (id != MIXCOM_ID ) {
182		ret=-ENODEV;
183		printk(KERN_WARNING "%s: no MixCOM board found at 0x%04lx\n",dev->name, dev->base_addr);
184		goto out;
185	}
186
187	vstr=inb_p(dev->base_addr + HSCX_VSTR) & 0x0f;
188	if(vstr>=sizeof(hscx_versions)/sizeof(char*) ||
189	    hscx_versions[vstr]==NULL) {
190		printk(KERN_WARNING "%s: board found but no HSCX chip detected at 0x%4lx (vstr = 0x%1x)\n",dev->name,dev->base_addr,vstr);
191		ret = -ENODEV;
192	} else {
193		printk(KERN_INFO "%s: HSCX chip version %s\n",dev->name,hscx_versions[vstr]);
194		ret = 0;
195	}
196
197out:
198
199	restore_flags(flags);
200	return ret;
201}
202
203
204static void mixcom_board_on(struct net_device *dev)
205{
206	outb_p(MIXCOM_OFF , MIXCOM_BOARD_BASE(dev) + MIXCOM_IT_OFFSET);
207	udelay(1000);
208	outb_p(mixcom_set_irq[dev->irq] | MIXCOM_ON,
209		MIXCOM_BOARD_BASE(dev) + MIXCOM_IT_OFFSET);
210	udelay(1000);
211}
212
213static void mixcom_board_off(struct net_device *dev)
214{
215	outb_p(MIXCOM_OFF , MIXCOM_BOARD_BASE(dev) + MIXCOM_IT_OFFSET);
216	udelay(1000);
217}
218
219static void mixcom_off(struct net_device *dev)
220{
221	wr_hscx(dev, HSCX_CCR1, 0x0);
222}
223
224static void mixcom_on(struct net_device *dev)
225{
226	struct comx_channel *ch = dev->priv;
227
228	wr_hscx(dev, HSCX_CCR1, HSCX_PU | HSCX_ODS | HSCX_ITF); // power up, push-pull
229	wr_hscx(dev, HSCX_CCR2, HSCX_CIE /* | HSCX_RIE */ );
230	wr_hscx(dev, HSCX_MODE, HSCX_TRANS | HSCX_ADM8 | HSCX_RAC | HSCX_RTS );
231	wr_hscx(dev, HSCX_RLCR, HSCX_RC | 47); // 1504 bytes
232	wr_hscx(dev, HSCX_MASK, HSCX_RSC | HSCX_TIN );
233	hscx_cmd(dev, HSCX_XRES | HSCX_RHR);
234
235	if (ch->HW_set_clock) ch->HW_set_clock(dev);
236
237}
238
239static int MIXCOM_send_packet(struct net_device *dev, struct sk_buff *skb)
240{
241	struct comx_channel *ch = dev->priv;
242	struct mixcom_privdata *hw = ch->HW_privdata;
243	unsigned long flags;
244
245	if (ch->debug_flags & DEBUG_HW_TX) {
246		comx_debug_bytes(dev, skb->data, skb->len, "MIXCOM_send_packet");
247	}
248
249	if (!(ch->line_status & LINE_UP)) {
250		return FRAME_DROPPED;
251	}
252
253	if (skb->len > HSCX_MTU) {
254		ch->stats.tx_errors++;
255		return FRAME_ERROR;
256	}
257
258	save_flags(flags); cli();
259
260	if (test_and_set_bit(0, &hw->txbusy)) {
261		printk(KERN_ERR "%s: transmitter called while busy... dropping frame (length %d)\n", dev->name, skb->len);
262		restore_flags(flags);
263		return FRAME_DROPPED;
264	}
265
266
267	hw->sending = skb;
268	hw->tx_ptr = 0;
269	hw->txbusy = 1;
270//	atomic_inc(&skb->users);	// save it
271	hscx_fill_fifo(dev);
272	restore_flags(flags);
273
274	ch->stats.tx_packets++;
275	ch->stats.tx_bytes += skb->len;
276
277	if (ch->debug_flags & DEBUG_HW_TX) {
278		comx_debug(dev, "MIXCOM_send_packet was successful\n\n");
279	}
280
281	return FRAME_ACCEPTED;
282}
283
284static inline void mixcom_receive_frame(struct net_device *dev)
285{
286	struct comx_channel *ch=dev->priv;
287	struct mixcom_privdata *hw=ch->HW_privdata;
288	register byte rsta;
289	register word length;
290
291	rsta = rd_hscx(dev, HSCX_RSTA) & (HSCX_VFR | HSCX_RDO |
292		HSCX_CRC | HSCX_RAB);
293	length = ((rd_hscx(dev, HSCX_RBCH) & 0x0f) << 8) |
294		rd_hscx(dev, HSCX_RBCL);
295
296	if ( length > hw->rx_ptr ) {
297		hscx_empty_fifo(dev, length - hw->rx_ptr);
298	}
299
300	if (!(rsta & HSCX_VFR)) {
301		ch->stats.rx_length_errors++;
302	}
303	if (rsta & HSCX_RDO) {
304		ch->stats.rx_over_errors++;
305	}
306	if (!(rsta & HSCX_CRC)) {
307		ch->stats.rx_crc_errors++;
308	}
309	if (rsta & HSCX_RAB) {
310		ch->stats.rx_frame_errors++;
311	}
312	ch->stats.rx_packets++;
313	ch->stats.rx_bytes += length;
314
315	if (rsta == (HSCX_VFR | HSCX_CRC) && hw->recving) {
316		skb_trim(hw->recving, hw->rx_ptr - 1);
317		if (ch->debug_flags & DEBUG_HW_RX) {
318			comx_debug_skb(dev, hw->recving,
319				"MIXCOM_interrupt receiving");
320		}
321		hw->recving->dev = dev;
322		if (ch->LINE_rx) {
323			ch->LINE_rx(dev, hw->recving);
324		}
325	}
326	else if(hw->recving) {
327		kfree_skb(hw->recving);
328	}
329	hw->recving = NULL;
330	hw->rx_ptr = 0;
331}
332
333
334static inline void mixcom_extended_interrupt(struct net_device *dev)
335{
336	struct comx_channel *ch=dev->priv;
337	struct mixcom_privdata *hw=ch->HW_privdata;
338	register byte exir;
339
340	exir = rd_hscx(dev, HSCX_EXIR) & (HSCX_XDU | HSCX_RFO | HSCX_CSC );
341
342	if (exir & HSCX_RFO) {
343		ch->stats.rx_over_errors++;
344		if (hw->rx_ptr) {
345			kfree_skb(hw->recving);
346			hw->recving = NULL; hw->rx_ptr = 0;
347		}
348		printk(KERN_ERR "MIXCOM: rx overrun\n");
349		hscx_cmd(dev, HSCX_RHR);
350	}
351
352	if (exir & HSCX_XDU) { // xmit underrun
353		ch->stats.tx_errors++;
354		ch->stats.tx_aborted_errors++;
355		if (hw->tx_ptr) {
356			kfree_skb(hw->sending);
357			hw->sending = NULL;
358			hw->tx_ptr = 0;
359		}
360		hscx_cmd(dev, HSCX_XRES);
361		clear_bit(0, &hw->txbusy);
362		if (ch->LINE_tx) {
363			ch->LINE_tx(dev);
364		}
365		printk(KERN_ERR "MIXCOM: tx underrun\n");
366	}
367
368	if (exir & HSCX_CSC) {
369		ch->stats.tx_carrier_errors++;
370		if ((rd_hscx(dev, HSCX_STAR) & HSCX_CTS) == 0) { // Vonal le
371			if (test_and_clear_bit(0, &ch->lineup_pending)) {
372               			del_timer(&ch->lineup_timer);
373			} else if (ch->line_status & LINE_UP) {
374        		       	ch->line_status &= ~LINE_UP;
375                		if (ch->LINE_status) {
376                      			ch->LINE_status(dev,ch->line_status);
377                      		}
378		      	}
379		}
380		if (!(ch->line_status & LINE_UP) && (rd_hscx(dev, HSCX_STAR) &
381		    HSCX_CTS)) { // Vonal fol
382			if (!test_and_set_bit(0,&ch->lineup_pending)) {
383				ch->lineup_timer.function = comx_lineup_func;
384	        	        ch->lineup_timer.data = (unsigned long)dev;
385        	        	ch->lineup_timer.expires = jiffies + HZ *
386        	        		ch->lineup_delay;
387	                	add_timer(&ch->lineup_timer);
388		                hscx_cmd(dev, HSCX_XRES);
389        		        clear_bit(0, &hw->txbusy);
390                		if (hw->sending) {
391					kfree_skb(hw->sending);
392				}
393				hw->sending=NULL;
394				hw->tx_ptr = 0;
395			}
396		}
397	}
398}
399
400
401static void MIXCOM_interrupt(int irq, void *dev_id, struct pt_regs *regs)
402{
403	unsigned long flags;
404	struct net_device *dev = (struct net_device *)dev_id;
405	struct comx_channel *ch, *twin_ch;
406	struct mixcom_privdata *hw, *twin_hw;
407	register unsigned char ista;
408
409	if (dev==NULL) {
410		printk(KERN_ERR "comx_interrupt: irq %d for unknown device\n",irq);
411		return;
412	}
413
414	ch = dev->priv;
415	hw = ch->HW_privdata;
416
417	save_flags(flags); cli();
418
419	while((ista = (rd_hscx(dev, HSCX_ISTA) & (HSCX_RME | HSCX_RPF |
420	    HSCX_XPR | HSCX_EXB | HSCX_EXA | HSCX_ICA)))) {
421		register byte ista2 = 0;
422
423		if (ista & HSCX_RME) {
424			mixcom_receive_frame(dev);
425		}
426		if (ista & HSCX_RPF) {
427			hscx_empty_fifo(dev, 32);
428		}
429		if (ista & HSCX_XPR) {
430			if (hw->tx_ptr) {
431				hscx_fill_fifo(dev);
432			} else {
433				clear_bit(0, &hw->txbusy);
434               			ch->LINE_tx(dev);
435			}
436		}
437
438		if (ista & HSCX_EXB) {
439			mixcom_extended_interrupt(dev);
440		}
441
442		if ((ista & HSCX_EXA) && ch->twin)  {
443			mixcom_extended_interrupt(ch->twin);
444		}
445
446		if ((ista & HSCX_ICA) && ch->twin &&
447		    (ista2 = rd_hscx(ch->twin, HSCX_ISTA) &
448		    (HSCX_RME | HSCX_RPF | HSCX_XPR ))) {
449			if (ista2 & HSCX_RME) {
450				mixcom_receive_frame(ch->twin);
451			}
452			if (ista2 & HSCX_RPF) {
453				hscx_empty_fifo(ch->twin, 32);
454			}
455			if (ista2 & HSCX_XPR) {
456				twin_ch=ch->twin->priv;
457				twin_hw=twin_ch->HW_privdata;
458				if (twin_hw->tx_ptr) {
459					hscx_fill_fifo(ch->twin);
460				} else {
461					clear_bit(0, &twin_hw->txbusy);
462					ch->LINE_tx(ch->twin);
463				}
464			}
465		}
466	}
467
468	restore_flags(flags);
469	return;
470}
471
472static int MIXCOM_open(struct net_device *dev)
473{
474	struct comx_channel *ch = dev->priv;
475	struct mixcom_privdata *hw = ch->HW_privdata;
476	struct proc_dir_entry *procfile = ch->procdir->subdir;
477	unsigned long flags;
478	int ret = -ENODEV;
479
480	if (!dev->base_addr || !dev->irq)
481		goto err_ret;
482
483
484	if(hw->channel==1) {
485		if(!TWIN(dev) || !(COMX_CHANNEL(TWIN(dev))->init_status &
486		    IRQ_ALLOCATED)) {
487			printk(KERN_ERR "%s: channel 0 not yet initialized\n",dev->name);
488			ret = -EAGAIN;
489			goto err_ret;
490		}
491	}
492
493
494	/* Is our hw present at all ? Not checking for channel 0 if it is already
495	   open */
496	if(hw->channel!=0 || !(ch->init_status & IRQ_ALLOCATED)) {
497		if (!request_region(dev->base_addr, MIXCOM_IO_EXTENT, dev->name)) {
498			ret = -EAGAIN;
499			goto err_ret;
500		}
501		if (mixcom_probe(dev)) {
502			ret = -ENODEV;
503			goto err_release_region;
504		}
505	}
506
507	if(hw->channel==0 && !(ch->init_status & IRQ_ALLOCATED)) {
508		if (request_irq(dev->irq, MIXCOM_interrupt, 0,
509		    dev->name, (void *)dev)) {
510			printk(KERN_ERR "MIXCOM: unable to obtain irq %d\n", dev->irq);
511			ret = -EAGAIN;
512			goto err_release_region;
513		}
514	}
515
516	save_flags(flags); cli();
517
518	if(hw->channel==0 && !(ch->init_status & IRQ_ALLOCATED)) {
519		ch->init_status|=IRQ_ALLOCATED;
520		mixcom_board_on(dev);
521	}
522
523	mixcom_on(dev);
524
525
526	hw->status=inb(MIXCOM_BOARD_BASE(dev) + MIXCOM_STATUS_OFFSET);
527	if(hw->status != 0xff) {
528		printk(KERN_DEBUG "%s: board has status register, good\n", dev->name);
529		hw->card_has_status=1;
530	}
531
532	hw->txbusy = 0;
533	ch->init_status |= HW_OPEN;
534
535	if (rd_hscx(dev, HSCX_STAR) & HSCX_CTS) {
536		ch->line_status |= LINE_UP;
537	} else {
538		ch->line_status &= ~LINE_UP;
539	}
540
541	restore_flags(flags);
542
543	ch->LINE_status(dev, ch->line_status);
544
545	for (; procfile ; procfile = procfile->next) {
546		if (strcmp(procfile->name, FILENAME_IO) == 0 ||
547		    strcmp(procfile->name, FILENAME_CHANNEL) == 0 ||
548		    strcmp(procfile->name, FILENAME_CLOCK) == 0 ||
549		    strcmp(procfile->name, FILENAME_IRQ) == 0) {
550			procfile->mode = S_IFREG |  0444;
551		}
552	}
553
554	return 0;
555
556err_release_region:
557	release_region(dev->base_addr, MIXCOM_IO_EXTENT);
558err_ret:
559	return ret;
560}
561
562static int MIXCOM_close(struct net_device *dev)
563{
564	struct comx_channel *ch = dev->priv;
565	struct mixcom_privdata *hw = ch->HW_privdata;
566	struct proc_dir_entry *procfile = ch->procdir->subdir;
567	unsigned long flags;
568
569
570	save_flags(flags); cli();
571
572	mixcom_off(dev);
573
574	/* This is channel 0, twin is not open, we can safely turn off everything */
575	if(hw->channel==0 && (!(TWIN(dev)) ||
576	    !(COMX_CHANNEL(TWIN(dev))->init_status & HW_OPEN))) {
577		mixcom_board_off(dev);
578		free_irq(dev->irq, dev);
579		release_region(dev->base_addr, MIXCOM_IO_EXTENT);
580		ch->init_status &= ~IRQ_ALLOCATED;
581	}
582
583	/* This is channel 1, channel 0 has already been shutdown, we can release
584	   this one too */
585	if(hw->channel==1 && !(COMX_CHANNEL(TWIN(dev))->init_status & HW_OPEN)) {
586		if(COMX_CHANNEL(TWIN(dev))->init_status & IRQ_ALLOCATED) {
587			mixcom_board_off(TWIN(dev));
588			free_irq(TWIN(dev)->irq, TWIN(dev));
589			release_region(TWIN(dev)->base_addr, MIXCOM_IO_EXTENT);
590			COMX_CHANNEL(TWIN(dev))->init_status &= ~IRQ_ALLOCATED;
591		}
592	}
593
594	/* the ioports for channel 1 can be safely released */
595	if(hw->channel==1) {
596		release_region(dev->base_addr, MIXCOM_IO_EXTENT);
597	}
598
599	restore_flags(flags);
600
601	/* If we don't hold any hardware open */
602	if(!(ch->init_status & IRQ_ALLOCATED)) {
603		for (; procfile ; procfile = procfile->next) {
604			if (strcmp(procfile->name, FILENAME_IO) == 0 ||
605			    strcmp(procfile->name, FILENAME_CHANNEL) == 0 ||
606			    strcmp(procfile->name, FILENAME_CLOCK) == 0 ||
607			    strcmp(procfile->name, FILENAME_IRQ) == 0) {
608				procfile->mode = S_IFREG |  0644;
609			}
610		}
611	}
612
613	/* channel 0 was only waiting for us to close channel 1
614	   close it completely */
615
616	if(hw->channel==1 && !(COMX_CHANNEL(TWIN(dev))->init_status & HW_OPEN)) {
617		for (procfile=COMX_CHANNEL(TWIN(dev))->procdir->subdir;
618		    procfile ; procfile = procfile->next) {
619			if (strcmp(procfile->name, FILENAME_IO) == 0 ||
620			    strcmp(procfile->name, FILENAME_CHANNEL) == 0 ||
621			    strcmp(procfile->name, FILENAME_CLOCK) == 0 ||
622			    strcmp(procfile->name, FILENAME_IRQ) == 0) {
623				procfile->mode = S_IFREG |  0644;
624			}
625		}
626	}
627
628	ch->init_status &= ~HW_OPEN;
629	return 0;
630}
631
632static int MIXCOM_statistics(struct net_device *dev,char *page)
633{
634	struct comx_channel *ch = dev->priv;
635	// struct mixcom_privdata *hw = ch->HW_privdata;
636	int len = 0;
637
638	if(ch->init_status && IRQ_ALLOCATED) {
639		len += sprintf(page + len, "Mixcom board: hardware open\n");
640	}
641
642	return len;
643}
644
645static int MIXCOM_dump(struct net_device *dev) {
646	return 0;
647}
648
649static int mixcom_read_proc(char *page, char **start, off_t off, int count,
650	int *eof, void *data)
651{
652	struct proc_dir_entry *file = (struct proc_dir_entry *)data;
653	struct net_device *dev = file->parent->data;
654	struct comx_channel *ch = dev->priv;
655	struct mixcom_privdata *hw = ch->HW_privdata;
656	int len = 0;
657
658	if (strcmp(file->name, FILENAME_IO) == 0) {
659		len = sprintf(page, "0x%x\n",
660			(unsigned int)MIXCOM_BOARD_BASE(dev));
661	} else if (strcmp(file->name, FILENAME_IRQ) == 0) {
662		len = sprintf(page, "%d\n", (unsigned int)dev->irq);
663	} else if (strcmp(file->name, FILENAME_CLOCK) == 0) {
664		if (hw->clock) len = sprintf(page, "%d\n", hw->clock);
665			else len = sprintf(page, "external\n");
666	} else if (strcmp(file->name, FILENAME_CHANNEL) == 0) {
667		len = sprintf(page, "%01d\n", hw->channel);
668	} else if (strcmp(file->name, FILENAME_TWIN) == 0) {
669		if (ch->twin) {
670			len = sprintf(page, "%s\n",ch->twin->name);
671		} else {
672			len = sprintf(page, "none\n");
673		}
674	} else {
675		printk(KERN_ERR "mixcom_read_proc: internal error, filename %s\n", file->name);
676		return -EBADF;
677	}
678
679	if (off >= len) {
680		*eof = 1;
681		return 0;
682	}
683	*start = page + off;
684	if (count >= len - off) *eof = 1;
685	return min_t(int, count, len - off);
686}
687
688
689static struct net_device *mixcom_twin_check(struct net_device *dev)
690{
691	struct comx_channel *ch = dev->priv;
692	struct proc_dir_entry *procfile = ch->procdir->parent->subdir;
693	struct mixcom_privdata *hw = ch->HW_privdata;
694
695	struct net_device *twin;
696	struct comx_channel *ch_twin;
697	struct mixcom_privdata *hw_twin;
698
699
700	for ( ; procfile ; procfile = procfile->next) {
701		if(!S_ISDIR(procfile->mode)) continue;
702
703        	twin = procfile->data;
704	        ch_twin = twin->priv;
705        	hw_twin = ch_twin->HW_privdata;
706
707
708	        if (twin != dev && dev->irq && dev->base_addr &&
709        	    dev->irq == twin->irq &&
710        	    ch->hardware == ch_twin->hardware &&
711		    dev->base_addr == twin->base_addr +
712		    (1-2*hw->channel)*MIXCOM_CHANNEL_OFFSET &&
713		    hw->channel == (1 - hw_twin->channel)) {
714	        	if  (!TWIN(twin) || TWIN(twin)==dev) {
715	        		return twin;
716	        	}
717		}
718        }
719	return NULL;
720}
721
722
723static void setup_twin(struct net_device* dev)
724{
725
726	if(TWIN(dev) && TWIN(TWIN(dev))) {
727		TWIN(TWIN(dev))=NULL;
728	}
729	if ((TWIN(dev) = mixcom_twin_check(dev)) != NULL) {
730		if (TWIN(TWIN(dev)) && TWIN(TWIN(dev)) != dev) {
731			TWIN(dev)=NULL;
732		} else {
733			TWIN(TWIN(dev))=dev;
734		}
735	}
736}
737
738static int mixcom_write_proc(struct file *file, const char *buffer,
739	u_long count, void *data)
740{
741	struct proc_dir_entry *entry = (struct proc_dir_entry *)data;
742	struct net_device *dev = (struct net_device *)entry->parent->data;
743	struct comx_channel *ch = dev->priv;
744	struct mixcom_privdata *hw = ch->HW_privdata;
745	char *page;
746	int value;
747
748	if (!(page = (char *)__get_free_page(GFP_KERNEL))) {
749		return -ENOMEM;
750	}
751
752	copy_from_user(page, buffer, count = min_t(unsigned long, count, PAGE_SIZE));
753	if (*(page + count - 1) == '\n') {
754		*(page + count - 1) = 0;
755	}
756
757	if (strcmp(entry->name, FILENAME_IO) == 0) {
758		value = simple_strtoul(page, NULL, 0);
759		if (value != 0x180 && value != 0x280 && value != 0x380) {
760			printk(KERN_ERR "MIXCOM: incorrect io address!\n");
761		} else {
762			dev->base_addr = MIXCOM_DEV_BASE(value,hw->channel);
763		}
764	} else if (strcmp(entry->name, FILENAME_IRQ) == 0) {
765		value = simple_strtoul(page, NULL, 0);
766		if (value < 0 || value > 15 || mixcom_set_irq[value]==0xFF) {
767			printk(KERN_ERR "MIXCOM: incorrect irq value!\n");
768		} else {
769			dev->irq = value;
770		}
771	} else if (strcmp(entry->name, FILENAME_CLOCK) == 0) {
772		if (strncmp("ext", page, 3) == 0) {
773			hw->clock = 0;
774		} else {
775			int kbps;
776
777			kbps = simple_strtoul(page, NULL, 0);
778			if (!kbps) {
779				hw->clock = 0;
780			} else {
781				hw->clock = kbps;
782			}
783			if (hw->clock < 32 || hw->clock > 2000) {
784				hw->clock = 0;
785				printk(KERN_ERR "MIXCOM: invalid clock rate!\n");
786			}
787		}
788		if (ch->init_status & HW_OPEN && ch->HW_set_clock) {
789			ch->HW_set_clock(dev);
790		}
791	} else if (strcmp(entry->name, FILENAME_CHANNEL) == 0) {
792		value = simple_strtoul(page, NULL, 0);
793        	if (value > 2) {
794                	printk(KERN_ERR "Invalid channel number\n");
795	        } else {
796        		dev->base_addr+=(hw->channel - value) * MIXCOM_CHANNEL_OFFSET;
797	        	hw->channel = value;
798		}
799	} else {
800		printk(KERN_ERR "hw_read_proc: internal error, filename %s\n",
801			entry->name);
802		return -EBADF;
803	}
804
805	setup_twin(dev);
806
807	free_page((unsigned long)page);
808	return count;
809}
810
811static int MIXCOM_init(struct net_device *dev) {
812	struct comx_channel *ch = dev->priv;
813	struct mixcom_privdata *hw;
814	struct proc_dir_entry *new_file;
815
816	if ((ch->HW_privdata = kmalloc(sizeof(struct mixcom_privdata),
817	    GFP_KERNEL)) == NULL) {
818	    	return -ENOMEM;
819	}
820
821	memset(hw = ch->HW_privdata, 0, sizeof(struct mixcom_privdata));
822
823	if ((new_file = create_proc_entry(FILENAME_IO, S_IFREG | 0644,
824	    ch->procdir)) == NULL) {
825		goto cleanup_HW_privdata;
826	}
827	new_file->data = (void *)new_file;
828	new_file->read_proc = &mixcom_read_proc;
829	new_file->write_proc = &mixcom_write_proc;
830	new_file->nlink = 1;
831
832	if ((new_file = create_proc_entry(FILENAME_IRQ, S_IFREG | 0644,
833	    ch->procdir)) == NULL) {
834	    	goto cleanup_filename_io;
835	}
836	new_file->data = (void *)new_file;
837	new_file->read_proc = &mixcom_read_proc;
838	new_file->write_proc = &mixcom_write_proc;
839	new_file->nlink = 1;
840
841
842	if ((new_file = create_proc_entry(FILENAME_CHANNEL, S_IFREG | 0644,
843	    ch->procdir)) == NULL) {
844	    	goto cleanup_filename_irq;
845	}
846	new_file->data = (void *)new_file;
847	new_file->read_proc = &mixcom_read_proc;
848	new_file->write_proc = &mixcom_write_proc;
849	new_file->nlink = 1;
850
851	if ((new_file = create_proc_entry(FILENAME_TWIN, S_IFREG | 0444,
852	    ch->procdir)) == NULL) {
853	    	goto cleanup_filename_channel;
854	}
855	new_file->data = (void *)new_file;
856	new_file->read_proc = &mixcom_read_proc;
857	new_file->write_proc = &mixcom_write_proc;
858	new_file->nlink = 1;
859
860	setup_twin(dev);
861
862	/* Fill in ch_struct hw specific pointers */
863	ch->HW_access_board = NULL;
864	ch->HW_release_board = NULL;
865	ch->HW_txe = MIXCOM_txe;
866	ch->HW_open = MIXCOM_open;
867	ch->HW_close = MIXCOM_close;
868	ch->HW_send_packet = MIXCOM_send_packet;
869	ch->HW_statistics = MIXCOM_statistics;
870	ch->HW_set_clock = NULL;
871
872	dev->base_addr = MIXCOM_DEV_BASE(MIXCOM_DEFAULT_IO,0);
873	dev->irq = MIXCOM_DEFAULT_IRQ;
874
875	MOD_INC_USE_COUNT;
876	return 0;
877cleanup_filename_channel:
878	remove_proc_entry(FILENAME_CHANNEL, ch->procdir);
879cleanup_filename_irq:
880	remove_proc_entry(FILENAME_IRQ, ch->procdir);
881cleanup_filename_io:
882	remove_proc_entry(FILENAME_IO, ch->procdir);
883cleanup_HW_privdata:
884	kfree(ch->HW_privdata);
885	return -EIO;
886}
887
888static int MIXCOM_exit(struct net_device *dev)
889{
890	struct comx_channel *ch = dev->priv;
891	struct mixcom_privdata *hw = ch->HW_privdata;
892
893	if(hw->channel==0 && TWIN(dev)) {
894		return -EBUSY;
895	}
896
897	if(hw->channel==1 && TWIN(dev)) {
898		TWIN(TWIN(dev))=NULL;
899	}
900
901	kfree(ch->HW_privdata);
902	remove_proc_entry(FILENAME_IO, ch->procdir);
903	remove_proc_entry(FILENAME_IRQ, ch->procdir);
904	remove_proc_entry(FILENAME_CHANNEL, ch->procdir);
905	remove_proc_entry(FILENAME_TWIN, ch->procdir);
906
907	MOD_DEC_USE_COUNT;
908	return 0;
909}
910
911static struct comx_hardware mixcomhw = {
912	"mixcom",
913	VERSION,
914	MIXCOM_init,
915	MIXCOM_exit,
916	MIXCOM_dump,
917	NULL
918};
919
920/* Module management */
921
922#ifdef MODULE
923#define comx_hw_mixcom_init init_module
924#endif
925
926int __init comx_hw_mixcom_init(void)
927{
928	return(comx_register_hardware(&mixcomhw));
929}
930
931#ifdef MODULE
932void
933cleanup_module(void)
934{
935	comx_unregister_hardware("mixcom");
936}
937#endif
938