summaryrefslogtreecommitdiffstats
path: root/app/atkbd.c
blob: 9091636ef5a58299167e043f3c8bd415315a7d58 (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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
#include "project.h"


#define KBCLK		(1UL << 0)
#define KBCLK_PORT	GPIOA
#define KBCLK_IRQ	NVIC_EXTI0_IRQ

#define KBDAT		(1UL << 1)
#define KBDAT_PORT	GPIOA

/*Cope with lost sync */
/*If we've had no bits for this long, it's definately the start of a new word,  72MHz units */
#define RESYNC_GAP 	(7200000)   /* 100ms */

#define ATKBD_TIMEOUT	1000      /*how long to wait for the keyboard to respond to a command in ms */

#define ATKBD_CLOCK_SEIZE_DELAY 200 /* how many us from pulling clock low to pulling data low, when we xmit */
#define ATKBD_BIT_DELAY	10      /* how many us we allow from the KBD to pull clock back up */


typedef enum
{
  STATE_START = 0,
  STATE_BIT0,
  STATE_BIT1,
  STATE_BIT2,
  STATE_BIT3,
  STATE_BIT4,
  STATE_BIT5,
  STATE_BIT6,
  STATE_BIT7,
  STATE_PARITY,
  STATE_STOP,
} atkbd_state;


#define ATKBD_CMD_SETLEDS       0xed
#define ATKBD_CMD_GSCANSET      0xf0
#define ATKBD_CMD_SSCANSET      0xf0
#define ATKBD_CMD_GETID         0xf2
#define ATKBD_CMD_SETREP        0xf3
#define ATKBD_CMD_ENABLE        0xf4
#define ATKBD_CMD_RESET_DIS     0xf5
#define ATKBD_CMD_RESET_DEF     0xf6
#define ATKBD_CMD_SETALL_MB     0xf8
#define ATKBD_CMD_SETALL_MBR    0xfa
#define ATKBD_CMD_RESET_BAT     0xff
#define ATKBD_CMD_RESEND        0xfe
#define ATKBD_CMD_EX_ENABLE     0xea
#define ATKBD_CMD_EX_SETLEDS    0xeb
#define ATKBD_CMD_OK_GETID      0xe8
#define ATKBD_CMD_ECHO          0xee

#define ATKBD_RET_ECHO          0xee
#define ATKBD_RET_ACK           0xfa
#define ATKBD_RET_NAK           0xfe
#define ATKBD_RET_BAT           0xaa
#define ATKBD_RET_EMUL0         0xe0
#define ATKBD_RET_EMUL1         0xe1
#define ATKBD_RET_RELEASE       0xf0
#define ATKBD_RET_HANJA         0xf1
#define ATKBD_RET_HANGEUL       0xf2
#define ATKBD_RET_ERR           0xff

#define ATKBD_KEY_UNKNOWN       0
#define ATKBD_KEY_NULL          255




static int atkbd_ack;
static int atkbd_nack;
static int atkbd_bat;
static int atkbd_echo;

static void
atkbd_mask_irq (void)
{
  nvic_disable_irq (KBCLK_IRQ);
}

static void
atkbd_unmask_flush_irq (void)
{
  exti_reset_request (KBCLK);
  nvic_enable_irq (KBCLK_IRQ);
}



static uint32_t
cycle_diff (uint32_t a, uint32_t b)
{
  return b - a;
}

static void
atkbd_dispatch (int key, int updown)
{
/* the logic here is batshit, consult scancode.doc in the DOCS dir */
  static int fake_ctrl, pause_down;

  switch (key)
    {
    case AT_SC_LEFTCTRL | AT_BS_EMUL1:
      fake_ctrl = updown;
      break;
    }


  if (key == (AT_BS_EMUL0 | AT_SC_KPASTERISK))
    key = AT_SC_SYSRQ;

  if (key == AT_SC_NUMLOCK)
    {
      /*Grr broken MS state machine in docs */
      if ((!updown) && (pause_down))
        {
          key = AT_SC_PAUSE;
          pause_down = 0;
        }

      if (fake_ctrl && updown)
        {
          key = AT_SC_PAUSE;
          pause_down = 1;
        }
    }


/* Filter fakes */
  switch (key)
    {
    case AT_SC_LEFTCTRL | AT_BS_EMUL1:
    case AT_SC_LEFTSHIFT | AT_BS_EMUL0:
    case AT_SC_RIGHTSHIFT | AT_BS_EMUL0:
      break;
    default:
      scancode_dispatch (key, updown);
    }

}

static void
atkbd_data_dispatch (uint8_t byte)
{
  static int release;
  static int emul;

#ifdef DEBUG
  printf ("ATKBD < 0x%02x\r\n", byte);
#endif

  switch (byte)
    {
    case ATKBD_RET_ACK:
      atkbd_ack++;
      break;
    case ATKBD_RET_NAK:
      atkbd_nack++;
      break;
    case ATKBD_RET_BAT:
      atkbd_bat++;
      break;
    case ATKBD_RET_ECHO:
      atkbd_echo++;
      break;
    case ATKBD_RET_ERR:
    case ATKBD_KEY_UNKNOWN:
      /*All these need no action */
      break;
    case AT_SC_EMUL0:
      emul = AT_BS_EMUL0;
      break;
    case AT_SC_EMUL1:
      emul = AT_BS_EMUL1;
      break;
    case ATKBD_RET_RELEASE:
      release = 1;
      break;
    default:
      atkbd_dispatch (emul | byte, !release);
      emul = 0;
      release = 0;
    }

}


void
exti0_isr (void)
{
  static uint32_t last_interrupt = 0;
  static atkbd_state state = STATE_START;
  static uint8_t atkbd_byte;
  static int parity;

  uint32_t now, diff;
  int d;

  d = ! !GET (KBDAT);

  exti_reset_request (KBCLK);

  now = dwt_read_cycle_counter ();
  diff = cycle_diff (last_interrupt, now);
  last_interrupt = now;


  if (diff > RESYNC_GAP)
    state = STATE_START;


  switch (state)
    {
    case STATE_START:
      atkbd_byte = 0;
      parity = 0;
      if (!d)
        state++;
      break;
    case STATE_BIT0:
    case STATE_BIT1:
    case STATE_BIT2:
    case STATE_BIT3:
    case STATE_BIT4:
    case STATE_BIT5:
    case STATE_BIT6:
    case STATE_BIT7:
      atkbd_byte |= d << (state - STATE_BIT0);
      /* fall through */
    case STATE_PARITY:
      parity ^= d;
      state++;
      break;
    case STATE_STOP:
      if (d && parity)
        atkbd_data_dispatch (atkbd_byte);
      state = STATE_START;
      break;
    }
}



void
atkbd_set (int clk, int dat)
{
  if (clk)
    {
      MAP_INPUT_PU (KBCLK);
    }
  else
    {
      CLEAR (KBCLK);
      MAP_OUTPUT_PP (KBCLK);
    }


  if (dat)
    {
      MAP_INPUT_PU (KBDAT);
    }
  else
    {
      CLEAR (KBDAT);
      MAP_OUTPUT_PP (KBDAT);
    }

}


int
atkbd_send (uint8_t d)
{
  uint32_t then = ticks;
  int parity = 1;
  uint32_t c;

  atkbd_mask_irq ();

  atkbd_set (0, 1);
  delay_us (ATKBD_CLOCK_SEIZE_DELAY);
  atkbd_set (0, 0);
  delay_us (ATKBD_BIT_DELAY);
  atkbd_set (1, 0);
  delay_us (ATKBD_BIT_DELAY);


  /* 8 data bits */
  for (c = 1; c < 0x100; c <<= 1)
    {
      while (GET (KBCLK))
        if (timed_out (then, ATKBD_TIMEOUT))
          goto err;
      atkbd_set (1, c & d);
      parity ^= ! !(c & d);
      while (!GET (KBCLK))
        if (timed_out (then, ATKBD_TIMEOUT))
          goto err;
    }

  /* A parity bit */
  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, parity);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  /*  two stop bits */
  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, 1);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  while (GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  atkbd_set (1, 1);
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  while (!GET (KBDAT))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  atkbd_unmask_flush_irq ();

#ifdef DEBUG
  printf ("ATKBD > 0x%02x\r\n", d);
#endif

  return 0;

err:
  atkbd_set (1, 1);
  while (!GET (KBDAT))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;
  while (!GET (KBCLK))
    if (timed_out (then, ATKBD_TIMEOUT))
      goto err;

  atkbd_unmask_flush_irq ();

#ifdef DEBUG
  printf ("ATKBD >! 0x%02x\r\n", d);
#endif
  return -1;
}

static int
atkbd_reset (void)
{
  uint32_t then = ticks;
  atkbd_bat = 0;
  atkbd_send (ATKBD_CMD_RESET_BAT);
  while (!atkbd_bat)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MBR);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;


  return 0;
}

int
atkbd_request_echo (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_ECHO);
  while (!atkbd_echo)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


int
atkbd_set_leds (uint8_t leds)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETLEDS);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (leds);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


int
atkbd_set_scanset (uint8_t scanset)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SSCANSET);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  then = ticks;
  atkbd_ack = 0;
  atkbd_send (scanset);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}

int
atkbd_set_mb (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MB);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}

int
atkbd_set_mbr (void)
{
  uint32_t then = ticks;
  atkbd_ack = 0;
  atkbd_send (ATKBD_CMD_SETALL_MBR);
  while (!atkbd_ack)
    if (timed_out (then, ATKBD_TIMEOUT))
      return -1;

  return 0;
}


void
atkbd_init (void)
{
  atkbd_set (1, 1);
  delay_ms (200);

  nvic_enable_irq (NVIC_EXTI0_IRQ);


  exti_select_source (KBCLK, KBCLK_PORT);
  exti_set_trigger (KBCLK, EXTI_TRIGGER_FALLING);
  exti_enable_request (KBCLK);

  exti_reset_request (KBCLK);
  nvic_enable_irq (KBCLK_IRQ);


  atkbd_reset ();
  atkbd_request_echo ();
  atkbd_set_mbr ();
  atkbd_set_scanset (2);
  atkbd_set_leds (0);


}