File: /usr/src/linux/drivers/atm/idt77105.c

1     /* drivers/atm/idt77105.c - IDT77105 (PHY) driver */
2      
3     /* Written 1999 by Greg Banks, NEC Australia <gnb@linuxfan.com>. Based on suni.c */
4     
5     
6     #include <linux/module.h>
7     #include <linux/sched.h>
8     #include <linux/kernel.h>
9     #include <linux/mm.h>
10     #include <linux/errno.h>
11     #include <linux/atmdev.h>
12     #include <linux/sonet.h>
13     #include <linux/delay.h>
14     #include <linux/timer.h>
15     #include <linux/init.h>
16     #include <linux/capability.h>
17     #include <linux/atm_idt77105.h>
18     #include <asm/system.h>
19     #include <asm/param.h>
20     #include <asm/uaccess.h>
21     
22     #include "idt77105.h"
23     
24     #undef GENERAL_DEBUG
25     
26     #ifdef GENERAL_DEBUG
27     #define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
28     #else
29     #define DPRINTK(format,args...)
30     #endif
31     
32     
33     struct idt77105_priv {
34     	struct idt77105_stats stats;    /* link diagnostics */
35     	struct atm_dev *dev;		/* device back-pointer */
36     	struct idt77105_priv *next;
37             int loop_mode;
38             unsigned char old_mcr;          /* storage of MCR reg while signal lost */
39     };
40     
41     
42     #define PRIV(dev) ((struct idt77105_priv *) dev->phy_data)
43     
44     #define PUT(val,reg) dev->ops->phy_put(dev,val,IDT77105_##reg)
45     #define GET(reg) dev->ops->phy_get(dev,IDT77105_##reg)
46     
47     static void idt77105_stats_timer_func(unsigned long);
48     static void idt77105_restart_timer_func(unsigned long);
49     
50     
51     static struct timer_list stats_timer = {
52         function:	&idt77105_stats_timer_func
53     };
54     static struct timer_list restart_timer = {
55         function:	&idt77105_restart_timer_func
56     };
57     static int start_timer = 1;
58     static struct idt77105_priv *idt77105_all = NULL;
59     
60     /*
61      * Retrieve the value of one of the IDT77105's counters.
62      * `counter' is one of the IDT77105_CTRSEL_* constants.
63      */
64     static u16 get_counter(struct atm_dev *dev, int counter)
65     {
66             u16 val;
67             
68             /* write the counter bit into PHY register 6 */
69             PUT(counter, CTRSEL);
70             /* read the low 8 bits from register 4 */
71             val = GET(CTRLO);
72             /* read the high 8 bits from register 5 */
73             val |= GET(CTRHI)<<8;
74             
75             return val;
76     }
77     
78     /*
79      * Timer function called every second to gather statistics
80      * from the 77105. This is done because the h/w registers
81      * will overflow if not read at least once per second. The
82      * kernel's stats are much higher precision. Also, having
83      * a separate copy of the stats allows implementation of
84      * an ioctl which gathers the stats *without* zero'ing them.
85      */
86     static void idt77105_stats_timer_func(unsigned long dummy)
87     {
88     	struct idt77105_priv *walk;
89     	struct atm_dev *dev;
90     	struct idt77105_stats *stats;
91     
92             DPRINTK("IDT77105 gathering statistics\n");
93     	for (walk = idt77105_all; walk; walk = walk->next) {
94     		dev = walk->dev;
95                     
96     		stats = &walk->stats;
97                     stats->symbol_errors += get_counter(dev, IDT77105_CTRSEL_SEC);
98                     stats->tx_cells += get_counter(dev, IDT77105_CTRSEL_TCC);
99                     stats->rx_cells += get_counter(dev, IDT77105_CTRSEL_RCC);
100                     stats->rx_hec_errors += get_counter(dev, IDT77105_CTRSEL_RHEC);
101     	}
102             if (!start_timer) mod_timer(&stats_timer,jiffies+IDT77105_STATS_TIMER_PERIOD);
103     }
104     
105     
106     /*
107      * A separate timer func which handles restarting PHY chips which
108      * have had the cable re-inserted after being pulled out. This is
109      * done by polling the Good Signal Bit in the Interrupt Status
110      * register every 5 seconds. The other technique (checking Good
111      * Signal Bit in the interrupt handler) cannot be used because PHY
112      * interrupts need to be disabled when the cable is pulled out
113      * to avoid lots of spurious cell error interrupts.
114      */
115     static void idt77105_restart_timer_func(unsigned long dummy)
116     {
117     	struct idt77105_priv *walk;
118     	struct atm_dev *dev;
119             unsigned char istat;
120     
121             DPRINTK("IDT77105 checking for cable re-insertion\n");
122     	for (walk = idt77105_all; walk; walk = walk->next) {
123     		dev = walk->dev;
124                     
125                     if (dev->signal != ATM_PHY_SIG_LOST)
126                         continue;
127                         
128                     istat = GET(ISTAT); /* side effect: clears all interrupt status bits */
129                     if (istat & IDT77105_ISTAT_GOODSIG) {
130                         /* Found signal again */
131                         dev->signal = ATM_PHY_SIG_FOUND;
132     	            printk(KERN_NOTICE "%s(itf %d): signal detected again\n",
133                             dev->type,dev->number);
134                         /* flush the receive FIFO */
135                         PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG);
136                         /* re-enable interrupts */
137     	            PUT( walk->old_mcr ,MCR);
138                     }
139     	}
140             if (!start_timer) mod_timer(&restart_timer,jiffies+IDT77105_RESTART_TIMER_PERIOD);
141     }
142     
143     
144     static int fetch_stats(struct atm_dev *dev,struct idt77105_stats *arg,int zero)
145     {
146     	unsigned long flags;
147     	struct idt77105_stats stats;
148     
149     	save_flags(flags);
150     	cli();
151     	memcpy(&stats, &PRIV(dev)->stats, sizeof(struct idt77105_stats));
152     	if (zero)
153     		memset(&PRIV(dev)->stats, 0, sizeof(struct idt77105_stats));
154     	restore_flags(flags);
155     	if (arg == NULL)
156     		return 0;
157     	return copy_to_user(arg, &PRIV(dev)->stats,
158     		    sizeof(struct idt77105_stats)) ? -EFAULT : 0;
159     }
160     
161     
162     static int set_loopback(struct atm_dev *dev,int mode)
163     {
164     	int diag;
165     
166     	diag = GET(DIAG) & ~IDT77105_DIAG_LCMASK;
167     	switch (mode) {
168     		case ATM_LM_NONE:
169     			break;
170     		case ATM_LM_LOC_ATM:
171     			diag |= IDT77105_DIAG_LC_PHY_LOOPBACK;
172     			break;
173     		case ATM_LM_RMT_ATM:
174     			diag |= IDT77105_DIAG_LC_LINE_LOOPBACK;
175     			break;
176     		default:
177     			return -EINVAL;
178     	}
179     	PUT(diag,DIAG);
180     	printk(KERN_NOTICE "%s(%d) Loopback mode is: %s\n", dev->type,
181     	    dev->number,
182     	    (mode == ATM_LM_NONE ? "NONE" : 
183     	      (mode == ATM_LM_LOC_ATM ? "DIAG (local)" :
184     		(mode == IDT77105_DIAG_LC_LINE_LOOPBACK ? "LOOP (remote)" :
185     		  "unknown")))
186     		    );
187     	PRIV(dev)->loop_mode = mode;
188     	return 0;
189     }
190     
191     
192     static int idt77105_ioctl(struct atm_dev *dev,unsigned int cmd,void *arg)
193     {
194             printk(KERN_NOTICE "%s(%d) idt77105_ioctl() called\n",dev->type,dev->number);
195     	switch (cmd) {
196     		case IDT77105_GETSTATZ:
197     			if (!capable(CAP_NET_ADMIN)) return -EPERM;
198     			/* fall through */
199     		case IDT77105_GETSTAT:
200     			return fetch_stats(dev,(struct idt77105_stats *) arg,
201     			    cmd == IDT77105_GETSTATZ);
202     		case ATM_SETLOOP:
203     			return set_loopback(dev,(int) (long) arg);
204     		case ATM_GETLOOP:
205     			return put_user(PRIV(dev)->loop_mode,(int *) arg) ?
206     			    -EFAULT : 0;
207     		case ATM_QUERYLOOP:
208     			return put_user(ATM_LM_LOC_ATM | ATM_LM_RMT_ATM,
209     			    (int *) arg) ? -EFAULT : 0;
210     		default:
211     			return -ENOIOCTLCMD;
212     	}
213     }
214     
215     
216     
217     static void idt77105_int(struct atm_dev *dev)
218     {
219             unsigned char istat;
220             
221             istat = GET(ISTAT); /* side effect: clears all interrupt status bits */
222          
223             DPRINTK("IDT77105 generated an interrupt, istat=%02x\n", (unsigned)istat);
224                     
225             if (istat & IDT77105_ISTAT_RSCC) {
226                 /* Rx Signal Condition Change - line went up or down */
227                 if (istat & IDT77105_ISTAT_GOODSIG) {   /* signal detected again */
228                     /* This should not happen (restart timer does it) but JIC */
229                     dev->signal = ATM_PHY_SIG_FOUND;
230                 } else {    /* signal lost */
231                     /*
232                      * Disable interrupts and stop all transmission and
233                      * reception - the restart timer will restore these.
234                      */
235                     PRIV(dev)->old_mcr = GET(MCR);
236     	        PUT(
237                         (PRIV(dev)->old_mcr|
238                         IDT77105_MCR_DREC|
239                         IDT77105_MCR_DRIC|
240                         IDT77105_MCR_HALTTX
241                         ) & ~IDT77105_MCR_EIP, MCR);
242                     dev->signal = ATM_PHY_SIG_LOST;
243     	        printk(KERN_NOTICE "%s(itf %d): signal lost\n",
244                         dev->type,dev->number);
245                 }
246             }
247             
248             if (istat & IDT77105_ISTAT_RFO) {
249                 /* Rx FIFO Overrun -- perform a FIFO flush */
250                 PUT( GET(DIAG) | IDT77105_DIAG_RFLUSH, DIAG);
251     	    printk(KERN_NOTICE "%s(itf %d): receive FIFO overrun\n",
252                     dev->type,dev->number);
253             }
254     #ifdef GENERAL_DEBUG
255             if (istat & (IDT77105_ISTAT_HECERR | IDT77105_ISTAT_SCR |
256                          IDT77105_ISTAT_RSE)) {
257                 /* normally don't care - just report in stats */
258     	    printk(KERN_NOTICE "%s(itf %d): received cell with error\n",
259                     dev->type,dev->number);
260             }
261     #endif
262     }
263     
264     
265     static int idt77105_start(struct atm_dev *dev)
266     {
267     	unsigned long flags;
268     
269     	if (!(PRIV(dev) = kmalloc(sizeof(struct idt77105_priv),GFP_KERNEL)))
270     		return -ENOMEM;
271     	PRIV(dev)->dev = dev;
272     	save_flags(flags);
273     	cli();
274     	PRIV(dev)->next = idt77105_all;
275     	idt77105_all = PRIV(dev);
276     	restore_flags(flags);
277     	memset(&PRIV(dev)->stats,0,sizeof(struct idt77105_stats));
278             
279             /* initialise dev->signal from Good Signal Bit */
280             dev->signal = GET(ISTAT) & IDT77105_ISTAT_GOODSIG ? ATM_PHY_SIG_FOUND :
281     	  ATM_PHY_SIG_LOST;
282     	if (dev->signal == ATM_PHY_SIG_LOST)
283     		printk(KERN_WARNING "%s(itf %d): no signal\n",dev->type,
284     		    dev->number);
285     
286             /* initialise loop mode from hardware */
287             switch ( GET(DIAG) & IDT77105_DIAG_LCMASK ) {
288             case IDT77105_DIAG_LC_NORMAL:
289                 PRIV(dev)->loop_mode = ATM_LM_NONE;
290                 break;
291             case IDT77105_DIAG_LC_PHY_LOOPBACK:
292                 PRIV(dev)->loop_mode = ATM_LM_LOC_ATM;
293                 break;
294             case IDT77105_DIAG_LC_LINE_LOOPBACK:
295                 PRIV(dev)->loop_mode = ATM_LM_RMT_ATM;
296                 break;
297             }
298             
299             /* enable interrupts, e.g. on loss of signal */
300             PRIV(dev)->old_mcr = GET(MCR);
301             if (dev->signal == ATM_PHY_SIG_FOUND) {
302                 PRIV(dev)->old_mcr |= IDT77105_MCR_EIP;
303     	    PUT(PRIV(dev)->old_mcr, MCR);
304             }
305     
306                         
307     	idt77105_stats_timer_func(0); /* clear 77105 counters */
308     	(void) fetch_stats(dev,NULL,1); /* clear kernel counters */
309             
310     	cli();
311     	if (!start_timer) restore_flags(flags);
312     	else {
313     		start_timer = 0;
314     		restore_flags(flags);
315                     
316     		init_timer(&stats_timer);
317     		stats_timer.expires = jiffies+IDT77105_STATS_TIMER_PERIOD;
318     		stats_timer.function = idt77105_stats_timer_func;
319     		add_timer(&stats_timer);
320                     
321     		init_timer(&restart_timer);
322     		restart_timer.expires = jiffies+IDT77105_RESTART_TIMER_PERIOD;
323     		restart_timer.function = idt77105_restart_timer_func;
324     		add_timer(&restart_timer);
325     	}
326     	return 0;
327     }
328     
329     
330     static const struct atmphy_ops idt77105_ops = {
331     	idt77105_start,
332     	idt77105_ioctl,
333     	idt77105_int
334     };
335     
336     
337     int __init idt77105_init(struct atm_dev *dev)
338     {
339     	MOD_INC_USE_COUNT;
340     
341     	dev->phy = &idt77105_ops;
342     	return 0;
343     }
344     
345     
346     /*
347      * TODO: this function should be called through phy_ops
348      * but that will not be possible for some time as there is
349      * currently a freeze on modifying that structure
350      * -- Greg Banks, 13 Sep 1999
351      */
352     int idt77105_stop(struct atm_dev *dev)
353     {
354     	struct idt77105_priv *walk, *prev;
355     
356             DPRINTK("%s(itf %d): stopping IDT77105\n",dev->type,dev->number);
357             
358             /* disable interrupts */
359     	PUT( GET(MCR) & ~IDT77105_MCR_EIP, MCR );
360             
361             /* detach private struct from atm_dev & free */
362     	for (prev = NULL, walk = idt77105_all ;
363                  walk != NULL;
364                  prev = walk, walk = walk->next) {
365                 if (walk->dev == dev) {
366                     if (prev != NULL)
367                         prev->next = walk->next;
368                     else
369                         idt77105_all = walk->next;
370     	        dev->phy = NULL;
371                     PRIV(dev) = NULL;
372                     kfree(walk);
373                     break;
374                 }
375             }
376     
377     	MOD_DEC_USE_COUNT;
378     	return 0;
379     }
380     
381     
382     
383     EXPORT_SYMBOL(idt77105_init);
384     EXPORT_SYMBOL(idt77105_stop);
385     
386     MODULE_LICENSE("GPL");
387     
388     #ifdef MODULE
389     
390     int init_module(void)
391     {
392     	return 0;
393     }
394     
395     
396     void cleanup_module(void)
397     {
398             /* turn off timers */
399             del_timer(&stats_timer);
400             del_timer(&restart_timer);
401     }
402     
403     #endif
404