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 <linux/slab.h>
  19#include <asm/system.h>
  20#include <asm/param.h>
  21#include <asm/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
  34struct 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
  42static 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
  49static void idt77105_stats_timer_func(unsigned long);
  50static void idt77105_restart_timer_func(unsigned long);
  51
  52
  53static DEFINE_TIMER(stats_timer, idt77105_stats_timer_func, 0, 0);
  54static DEFINE_TIMER(restart_timer, idt77105_restart_timer_func, 0, 0);
  55static int start_timer = 1;
  56static 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 */
  62static 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 */
  84static void idt77105_stats_timer_func(unsigned long dummy)
  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(&stats_timer,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 */
 113static void idt77105_restart_timer_func(unsigned long dummy)
 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(&restart_timer,jiffies+IDT77105_RESTART_TIMER_PERIOD);
 139}
 140
 141
 142static 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(&idt77105_priv_lock, flags);
 152        if (arg == NULL)
 153                return 0;
 154        return copy_to_user(arg, &stats,
 155                    sizeof(struct idt77105_stats)) ? -EFAULT : 0;
 156}
 157
 158
 159static 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
 189static 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                        /* fall through */
 196                case IDT77105_GETSTAT:
 197                        return fetch_stats(dev, arg, cmd == IDT77105_GETSTATZ);
 198                case ATM_SETLOOP:
 199                        return set_loopback(dev,(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
 213static 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
 261static int idt77105_start(struct atm_dev *dev)
 262{
 263        unsigned long flags;
 264
 265        if (!(dev->dev_data = kmalloc(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(&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(0); /* clear 77105 counters */
 304        (void) fetch_stats(dev,NULL,1); /* clear kernel counters */
 305        
 306        spin_lock_irqsave(&idt77105_priv_lock, flags);
 307        if (start_timer) {
 308                start_timer = 0;
 309                
 310                init_timer(&stats_timer);
 311                stats_timer.expires = jiffies+IDT77105_STATS_TIMER_PERIOD;
 312                stats_timer.function = idt77105_stats_timer_func;
 313                add_timer(&stats_timer);
 314                
 315                init_timer(&restart_timer);
 316                restart_timer.expires = jiffies+IDT77105_RESTART_TIMER_PERIOD;
 317                restart_timer.function = idt77105_restart_timer_func;
 318                add_timer(&restart_timer);
 319        }
 320        spin_unlock_irqrestore(&idt77105_priv_lock, flags);
 321        return 0;
 322}
 323
 324
 325static int idt77105_stop(struct atm_dev *dev)
 326{
 327        struct idt77105_priv *walk, *prev;
 328
 329        DPRINTK("%s(itf %d): stopping IDT77105\n",dev->type,dev->number);
 330        
 331        /* disable interrupts */
 332        PUT( GET(MCR) & ~IDT77105_MCR_EIP, MCR );
 333        
 334        /* detach private struct from atm_dev & free */
 335        for (prev = NULL, walk = idt77105_all ;
 336             walk != NULL;
 337             prev = walk, walk = walk->next) {
 338            if (walk->dev == dev) {
 339                if (prev != NULL)
 340                    prev->next = walk->next;
 341                else
 342                    idt77105_all = walk->next;
 343                dev->phy = NULL;
 344                dev->dev_data = NULL;
 345                kfree(walk);
 346                break;
 347            }
 348        }
 349
 350        return 0;
 351}
 352
 353
 354static const struct atmphy_ops idt77105_ops = {
 355        .start =        idt77105_start,
 356        .ioctl =        idt77105_ioctl,
 357        .interrupt =    idt77105_int,
 358        .stop =         idt77105_stop,
 359};
 360
 361
 362int idt77105_init(struct atm_dev *dev)
 363{
 364        dev->phy = &idt77105_ops;
 365        return 0;
 366}
 367
 368EXPORT_SYMBOL(idt77105_init);
 369
 370static void __exit idt77105_exit(void)
 371{
 372        /* turn off timers */
 373        del_timer(&stats_timer);
 374        del_timer(&restart_timer);
 375}
 376
 377module_exit(idt77105_exit);
 378
 379MODULE_LICENSE("GPL");
 380