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