1// SPDX-License-Identifier: GPL-2.0-only 2/* drivers/atm/uPD98402.c - NEC uPD98402 (PHY) declarations */ 3 4/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ 5 6 7#include <linux/module.h> 8#include <linux/mm.h> 9#include <linux/errno.h> 10#include <linux/atmdev.h> 11#include <linux/sonet.h> 12#include <linux/init.h> 13#include <linux/slab.h> 14#include <linux/uaccess.h> 15#include <linux/atomic.h> 16 17#include "uPD98402.h" 18 19 20#if 0 21#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) 22#else 23#define DPRINTK(format,args...) 24#endif 25 26 27struct uPD98402_priv { 28 struct k_sonet_stats sonet_stats;/* link diagnostics */ 29 unsigned char framing; /* SONET/SDH framing */ 30 int loop_mode; /* loopback mode */ 31 spinlock_t lock; 32}; 33 34 35#define PRIV(dev) ((struct uPD98402_priv *) dev->phy_data) 36 37#define PUT(val,reg) dev->ops->phy_put(dev,val,uPD98402_##reg) 38#define GET(reg) dev->ops->phy_get(dev,uPD98402_##reg) 39 40 41static int fetch_stats(struct atm_dev *dev,struct sonet_stats __user *arg,int zero) 42{ 43 struct sonet_stats tmp; 44 int error = 0; 45 46 atomic_add(GET(HECCT),&PRIV(dev)->sonet_stats.uncorr_hcs); 47 sonet_copy_stats(&PRIV(dev)->sonet_stats,&tmp); 48 if (arg) error = copy_to_user(arg,&tmp,sizeof(tmp)); 49 if (zero && !error) { 50 /* unused fields are reported as -1, but we must not "adjust" 51 them */ 52 tmp.corr_hcs = tmp.tx_cells = tmp.rx_cells = 0; 53 sonet_subtract_stats(&PRIV(dev)->sonet_stats,&tmp); 54 } 55 return error ? -EFAULT : 0; 56} 57 58 59static int set_framing(struct atm_dev *dev,unsigned char framing) 60{ 61 static const unsigned char sonet[] = { 1,2,3,0 }; 62 static const unsigned char sdh[] = { 1,0,0,2 }; 63 const char *set; 64 unsigned long flags; 65 66 switch (framing) { 67 case SONET_FRAME_SONET: 68 set = sonet; 69 break; 70 case SONET_FRAME_SDH: 71 set = sdh; 72 break; 73 default: 74 return -EINVAL; 75 } 76 spin_lock_irqsave(&PRIV(dev)->lock, flags); 77 PUT(set[0],C11T); 78 PUT(set[1],C12T); 79 PUT(set[2],C13T); 80 PUT((GET(MDR) & ~uPD98402_MDR_SS_MASK) | (set[3] << 81 uPD98402_MDR_SS_SHIFT),MDR); 82 spin_unlock_irqrestore(&PRIV(dev)->lock, flags); 83 return 0; 84} 85 86 87static int get_sense(struct atm_dev *dev,u8 __user *arg) 88{ 89 unsigned long flags; 90 unsigned char s[3]; 91 92 spin_lock_irqsave(&PRIV(dev)->lock, flags); 93 s[0] = GET(C11R); 94 s[1] = GET(C12R); 95 s[2] = GET(C13R); 96 spin_unlock_irqrestore(&PRIV(dev)->lock, flags); 97 return (put_user(s[0], arg) || put_user(s[1], arg+1) || 98 put_user(s[2], arg+2) || put_user(0xff, arg+3) || 99 put_user(0xff, arg+4) || put_user(0xff, arg+5)) ? -EFAULT : 0; 100} 101 102 103static int set_loopback(struct atm_dev *dev,int mode) 104{ 105 unsigned char mode_reg; 106 107 mode_reg = GET(MDR) & ~(uPD98402_MDR_TPLP | uPD98402_MDR_ALP | 108 uPD98402_MDR_RPLP); 109 switch (__ATM_LM_XTLOC(mode)) { 110 case __ATM_LM_NONE: 111 break; 112 case __ATM_LM_PHY: 113 mode_reg |= uPD98402_MDR_TPLP; 114 break; 115 case __ATM_LM_ATM: 116 mode_reg |= uPD98402_MDR_ALP; 117 break; 118 default: 119 return -EINVAL; 120 } 121 switch (__ATM_LM_XTRMT(mode)) { 122 case __ATM_LM_NONE: 123 break; 124 case __ATM_LM_PHY: 125 mode_reg |= uPD98402_MDR_RPLP; 126 break; 127 default: 128 return -EINVAL; 129 } 130 PUT(mode_reg,MDR); 131 PRIV(dev)->loop_mode = mode; 132 return 0; 133} 134 135 136static int uPD98402_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg) 137{ 138 switch (cmd) { 139 140 case SONET_GETSTATZ: 141 case SONET_GETSTAT: 142 return fetch_stats(dev,arg, cmd == SONET_GETSTATZ); 143 case SONET_SETFRAMING: 144 return set_framing(dev, (int)(unsigned long)arg); 145 case SONET_GETFRAMING: 146 return put_user(PRIV(dev)->framing,(int __user *)arg) ? 147 -EFAULT : 0; 148 case SONET_GETFRSENSE: 149 return get_sense(dev,arg); 150 case ATM_SETLOOP: 151 return set_loopback(dev, (int)(unsigned long)arg); 152 case ATM_GETLOOP: 153 return put_user(PRIV(dev)->loop_mode,(int __user *)arg) ? 154 -EFAULT : 0; 155 case ATM_QUERYLOOP: 156 return put_user(ATM_LM_LOC_PHY | ATM_LM_LOC_ATM | 157 ATM_LM_RMT_PHY,(int __user *)arg) ? -EFAULT : 0; 158 default: 159 return -ENOIOCTLCMD; 160 } 161} 162 163 164#define ADD_LIMITED(s,v) \ 165 { atomic_add(GET(v),&PRIV(dev)->sonet_stats.s); \ 166 if (atomic_read(&PRIV(dev)->sonet_stats.s) < 0) \ 167 atomic_set(&PRIV(dev)->sonet_stats.s,INT_MAX); } 168 169 170static void stat_event(struct atm_dev *dev) 171{ 172 unsigned char events; 173 174 events = GET(PCR); 175 if (events & uPD98402_PFM_PFEB) ADD_LIMITED(path_febe,PFECB); 176 if (events & uPD98402_PFM_LFEB) ADD_LIMITED(line_febe,LECCT); 177 if (events & uPD98402_PFM_B3E) ADD_LIMITED(path_bip,B3ECT); 178 if (events & uPD98402_PFM_B2E) ADD_LIMITED(line_bip,B2ECT); 179 if (events & uPD98402_PFM_B1E) ADD_LIMITED(section_bip,B1ECT); 180} 181 182 183#undef ADD_LIMITED 184 185 186static void uPD98402_int(struct atm_dev *dev) 187{ 188 static unsigned long silence = 0; 189 unsigned char reason; 190 191 while ((reason = GET(PICR))) { 192 if (reason & uPD98402_INT_LOS) 193 printk(KERN_NOTICE "%s(itf %d): signal lost\n", 194 dev->type,dev->number); 195 if (reason & uPD98402_INT_PFM) stat_event(dev); 196 if (reason & uPD98402_INT_PCO) { 197 (void) GET(PCOCR); /* clear interrupt cause */ 198 atomic_add(GET(HECCT), 199 &PRIV(dev)->sonet_stats.uncorr_hcs); 200 } 201 if ((reason & uPD98402_INT_RFO) && 202 (time_after(jiffies, silence) || silence == 0)) { 203 printk(KERN_WARNING "%s(itf %d): uPD98402 receive " 204 "FIFO overflow\n",dev->type,dev->number); 205 silence = (jiffies+HZ/2)|1; 206 } 207 } 208} 209 210 211static int uPD98402_start(struct atm_dev *dev) 212{ 213 DPRINTK("phy_start\n"); 214 if (!(dev->phy_data = kmalloc(sizeof(struct uPD98402_priv),GFP_KERNEL))) 215 return -ENOMEM; 216 spin_lock_init(&PRIV(dev)->lock); 217 memset(&PRIV(dev)->sonet_stats,0,sizeof(struct k_sonet_stats)); 218 (void) GET(PCR); /* clear performance events */ 219 PUT(uPD98402_PFM_FJ,PCMR); /* ignore frequency adj */ 220 (void) GET(PCOCR); /* clear overflows */ 221 PUT(~uPD98402_PCO_HECC,PCOMR); 222 (void) GET(PICR); /* clear interrupts */ 223 PUT(~(uPD98402_INT_PFM | uPD98402_INT_ALM | uPD98402_INT_RFO | 224 uPD98402_INT_LOS),PIMR); /* enable them */ 225 (void) fetch_stats(dev,NULL,1); /* clear kernel counters */ 226 atomic_set(&PRIV(dev)->sonet_stats.corr_hcs,-1); 227 atomic_set(&PRIV(dev)->sonet_stats.tx_cells,-1); 228 atomic_set(&PRIV(dev)->sonet_stats.rx_cells,-1); 229 return 0; 230} 231 232 233static int uPD98402_stop(struct atm_dev *dev) 234{ 235 /* let SAR driver worry about stopping interrupts */ 236 kfree(PRIV(dev)); 237 return 0; 238} 239 240 241static const struct atmphy_ops uPD98402_ops = { 242 .start = uPD98402_start, 243 .ioctl = uPD98402_ioctl, 244 .interrupt = uPD98402_int, 245 .stop = uPD98402_stop, 246}; 247 248 249int uPD98402_init(struct atm_dev *dev) 250{ 251DPRINTK("phy_init\n"); 252 dev->phy = &uPD98402_ops; 253 return 0; 254} 255 256 257MODULE_LICENSE("GPL"); 258 259EXPORT_SYMBOL(uPD98402_init); 260 261static __init int uPD98402_module_init(void) 262{ 263 return 0; 264} 265module_init(uPD98402_module_init); 266/* module_exit not defined so not unloadable */ 267