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