summaryrefslogtreecommitdiffstats
path: root/drivers/ap1000/plc.c
blob: b29b1a4c2d7bb078d012a1a84b3866ec06cc452b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
  /*
   * Copyright 1996 The Australian National University.
   * Copyright 1996 Fujitsu Laboratories Limited
   * 
   * This software may be distributed under the terms of the Gnu
   * Public License version 2 or later
  */
/*
 * Routines for controlling the Am79c864 physical layer controller.
 *
 * This chip implements some parts of the FDDI SMT standard
 * (PCM: physical connection management, LEM: link error monitor, etc.)
 * as well as the FDDI PHY standard.
 */
#include <linux/types.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include "apfddi.h"
#include "smt-types.h"
#include "am79c864.h"
#include "plc.h"
#include "apfddi-reg.h"

typedef enum {
    off,
    signalling,
    doing_lct,
    joining,
    active
} PlcPhase;
    
struct plc_state {
    LoopbackType	loopback;
    char		t_val[16];
    char		r_val[16];
    int			n;
    PortType		peer_type;
    PlcPhase		phase;
};

struct plc_info *this_plc_info;
struct plc_state this_plc_state;

void plc_init(struct plc_info *pip)
{
    int class, x;
    struct plc_state *psp = &this_plc_state;

    this_plc_info = pip;

    /* first turn it off, clear registers */
    class = pip->port_type == pt_s? CB_CLASS_S: 0;
    plc->ctrl_b = CB_PC_STOP + class;
    plc->intr_mask = IE_NP_ERROR;
    x = plc->intr_event;	/* these register clear when read */
    x = plc->viol_sym_ct;
    x = plc->min_idle_ct;
    x = plc->link_err_ct;

    /* initialize registers */
    plc->ctrl_a = 0;
    plc->ctrl_b = class;
    plc->c_min = pip->c_min >> 8;
    plc->tl_min = pip->tl_min >> 8;
    plc->tb_min = pip->tb_min >> 8;
    plc->t_out = pip->t_out >> 8;
    plc->t_scrub = pip->t_scrub >> 8;
    plc->ns_max = pip->ns_max >> 2;

    psp->phase = off;
}

int
plc_inited(struct plc_info *pip)
{
    int class, x;
    struct plc_state *psp = &this_plc_state;

    class = pip->port_type == pt_s? CB_CLASS_S: 0;
    if ((plc->ctrl_a & (CA_LOOPBACK|CA_FOT_OFF|CA_EB_LOOP|CA_LM_LOOP)) != 0)
	return 1;
    if ((plc->ctrl_b & (CB_CONFIG_CTRL|CB_CLASS_S|CB_PC_MAINT)) != class)
	return 2;
    if (plc->status_a & SA_SIG_DETECT)
	return 3;
    if ((plc->status_b & (SB_PCI_STATE|SB_PCM_STATE))
	 != (SB_PCI_STATE_INSERTED|SB_PCM_STATE_ACTIVE))
	return 4;

    /* all seems OK, reset the timers and counters just to be sure */
    plc->intr_mask = IE_NP_ERROR;
    x = plc->intr_event;	/* these register clear when read */
    x = plc->viol_sym_ct;
    x = plc->min_idle_ct;
    x = plc->link_err_ct;

    plc->c_min = pip->c_min >> 8;
    plc->tl_min = pip->tl_min >> 8;
    plc->tb_min = pip->tb_min >> 8;
    plc->t_out = pip->t_out >> 8;
    plc->t_scrub = pip->t_scrub >> 8;
    plc->ns_max = pip->ns_max >> 2;

    psp->phase = active;
    /* XXX should initialize other fields of this_plc_state */

    return 0;
}

void plc_sleep(void)
{
}

void pc_start(LoopbackType loopback)
{
    int x;
    struct plc_info *pip = this_plc_info;
    struct plc_state *psp = &this_plc_state;

    /* make sure it's off */
    plc->ctrl_b &= ~CB_PCM_CTRL;
    plc->ctrl_b |= CB_PC_STOP;

    /* set up loopback required */
    psp->loopback = loopback;
    x = 0;
    switch (loopback) {
    case loop_plc_lm:
	x = CA_LM_LOOP;
	break;
    case loop_plc_eb:
	x = CA_EB_LOOP;
	break;
    case loop_pdx:
	x = CA_LOOPBACK;
	break;
    default:
	x = 0;
    }
    plc->ctrl_a = x;

    /* set up bits to be exchanged */
    psp->t_val[0] = 0;
    psp->t_val[1] = ((int) pip->port_type >> 1) & 1;
    psp->t_val[2] = (int) pip->port_type & 1;
    psp->t_val[4] = 0;		/* XXX assume we want short LCT */
    psp->t_val[5] = 0;
    psp->t_val[6] = 0;		/* XXX too lazy to fire up my MAC for LCT */
    psp->t_val[8] = 0;		/* XXX don't wanna local loop */
    psp->t_val[9] = 1;		/* gotta MAC on port output */

    pc_restart();
}

void pc_restart(void)
{
    struct plc_state *psp = &this_plc_state;

    if (psp->phase != off)
	printk("restarting pcm\n");
    if (psp->phase == active)
	set_cf_join(0);		/* we're down :-( */

    psp->n = 0;
    plc->vec_length = 3 - 1;
    plc->xmit_vector = psp->t_val[0] + (psp->t_val[1] << 1)
	+ (psp->t_val[2] << 2);

    plc->intr_mask = IE_NP_ERROR | IE_PCM_BREAK | IE_PCM_CODE;
    plc->ctrl_b &= ~CB_PCM_CTRL;
    plc->ctrl_b |= CB_PC_START;	/* light blue paper and stand clear */

    psp->phase = signalling;
}

void pc_stop(void)
{
    struct plc_state *psp = &this_plc_state;

    if (psp->phase == active)
	set_cf_join(0);
    plc->ctrl_b &= ~CB_PCM_CTRL;
    plc->ctrl_b |= CB_PC_STOP;
    plc->intr_mask = IE_NP_ERROR;
    psp->phase = off;
}

void plc_poll(void)
{
    struct plc_state *psp = &this_plc_state;
    int events, i;

    if ((*csr0 & CS0_PHY_IRQ) == 0)
	return;
    events = plc->intr_event & plc->intr_mask;
    if (events & IE_NP_ERROR) {
	printk("plc: NP error!\n");
    }
    if (events & IE_PCM_BREAK) {
	i = plc->status_b & SB_BREAK_REASON;
	if (i > SB_BREAK_REASON_START) {
	    if (psp->phase == signalling || psp->phase == doing_lct)
		pcm_dump_rtcodes();
	    printk("pcm: break reason %d\n", i);
	    if (psp->phase != off)
		pc_restart();
	    /* XXX need to check for trace? */
	}
    }
    if (events & IE_PCM_CODE) {
	if (psp->phase == signalling)
	    pcm_pseudo_code();
	else if (psp->phase == doing_lct)
	    pcm_lct_done();
	else
	    printk("XXX pcm_code interrupt in phase %d?\n", psp->phase);
    }
    if (events & IE_PCM_ENABLED) {
	if (psp->phase == joining)
	    pcm_enabled();
	else
	    printk("XXX pcm_enabled interrupt in phase %d?\n", psp->phase);
    }
    if (events & IE_TRACE_PROP) {
	if (psp->phase == active)
	    pcm_trace_prop();
	else
	    printk("XXX trace_prop interrupt in phase %d\n", psp->phase);
    }
}

void pcm_pseudo_code(void)
{
    struct plc_info *pip = this_plc_info;
    struct plc_state *psp = &this_plc_state;
    int i, nb, lct, hislct;

    /* unpack the bits from the peer */
    nb = plc->vec_length + 1;
    i = plc->rcv_vector;
    do {
	psp->r_val[psp->n++] = i & 1;
	i >>= 1;
    } while (--nb > 0);

    /* send some more, do LCT, whatever */
    switch (psp->n) {
    case 3:
	/*
	 * Got escape flag, port type; send compatibility,
	 * LCT duration, MAC for LCT flag.
	 */
	if (psp->r_val[0]) {
	    /* help! what do I do now? */
	    pcm_dump_rtcodes();
	    pc_restart();
	    break;
	}
	psp->peer_type = (PortType) ((psp->r_val[1] << 1) + psp->r_val[2]);
	/* XXX we're type S, we talk to anybody */
	psp->t_val[3] = 1;

	plc->vec_length = 4 - 1;
	plc->xmit_vector = psp->t_val[3] + (psp->t_val[4] << 1)
	    + (psp->t_val[5] << 2) + (psp->t_val[6] << 3);
	break;

    case 7:
	/*
	 * Got compatibility, LCT duration, MAC for LCT flag;
	 * time to do the LCT.
	 */
	lct = (psp->t_val[4] << 1) + psp->t_val[5];
	hislct = (psp->r_val[4] << 1) + psp->r_val[5];
	if (hislct > lct)
	    lct = hislct;

	/* set LCT duration */
	switch (lct) {
	case 0:
	    plc->lc_length = pip->lc_short >> 8;
	    plc->ctrl_b &= ~CB_LONG_LCT;
	    break;
	case 1:
	    plc->lc_length = pip->lc_medium >> 8;
	    plc->ctrl_b &= ~CB_LONG_LCT;
	    break;
	case 2:
	    plc->ctrl_b |= CB_LONG_LCT;
	    /* XXX set up a timeout for pip->lc_long */
	    break;
	case 3:
	    plc->ctrl_b |= CB_LONG_LCT;
	    /* XXX set up a timeout for pip->lc_extended */
	    break;
	}

	/* start the LCT */
	i = plc->link_err_ct;	/* clear the register */
	plc->ctrl_b &= ~CB_PC_LCT;
	/* XXX assume we're not using the MAC for LCT;
	   if he's got a MAC, loop his stuff back, otherwise send idle. */
	if (psp->r_val[6])
	    plc->ctrl_b |= CB_PC_LCT_LOOP;
	else
	    plc->ctrl_b |= CB_PC_LCT_IDLE;
	psp->phase = doing_lct;
	break;

    case 8:
	/*
	 * Got LCT result, send MAC for local loop and MAC on port
	 * output flags.
	 */
	if (psp->t_val[7] || psp->r_val[7]) {
	    printk("LCT failed, restarting.\n");
	    /* LCT failed - do at least a medium length test next time. */
	    if (psp->t_val[4] == 0 && psp->t_val[5] == 0)
		psp->t_val[5] = 1;
	    pcm_dump_rtcodes();
	    pc_restart();
	    break;
	}
	plc->vec_length = 2 - 1;
	plc->xmit_vector = psp->t_val[8] + (psp->t_val[9] << 1);
	break;

    case 10:
	/*
	 * Got MAC for local loop and MAC on port output flags.
	 * Let's join.
	 */
	plc->intr_mask = IE_NP_ERROR | IE_PCM_BREAK | IE_PCM_ENABLED;
	plc->ctrl_b |= CB_PC_JOIN;
	psp->phase = joining;
	/* printk("pcm: joining\n"); */
	break;

    default:
	printk("pcm_pseudo_code bug: n = %d\n", psp->n);
    }
}

void pcm_lct_done(void)
{
    struct plc_state *psp = &this_plc_state;
    int i;

    i = plc->link_err_ct;
    psp->t_val[7] = i > 0;
    printk("pcm: lct %s (%d errors)\n", psp->t_val[7]? "failed": "passed", i);
    plc->ctrl_b &= ~(CB_PC_LCT | CB_LONG_LCT);
    plc->vec_length = 1 - 1;
    plc->xmit_vector = psp->t_val[7];
    psp->phase = signalling;
}

void pcm_dump_rtcodes(void)
{
    struct plc_state *psp = &this_plc_state;
    int i;

    if (psp->n > 0) {
	printk("pcm signalling interrupted after %d bits:\nt_val:", psp->n);
	for (i = 0; i < psp->n; ++i)
	    printk(" %d", psp->t_val[i]);
	printk("\nr_val:");
	for (i = 0; i < psp->n; ++i)
	    printk(" %d", psp->r_val[i]);
	printk("\n");
    }
}

void pcm_enabled(void)
{
    struct plc_state *psp = &this_plc_state;
    int i;

    printk("pcm: enabled\n");
    psp->phase = active;
    i = plc->link_err_ct;	/* clear the register */
    /* XXX should set up LEM here */
    /* XXX do we want to count violation symbols, minimum idle gaps,
       or elasticity buffer errors? */
    plc->intr_mask = IE_NP_ERROR | IE_PCM_BREAK | IE_TRACE_PROP;
    set_cf_join(1);		/* we're up :-) */
}

void pcm_trace_prop(void)
{
    /* XXX help! what do I do now? */
    pc_stop();
}