#include <stdbool.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "ps2.h"
#include "ps2_io.h"
#include "print.h"
#define WAIT(stat, us, err) \
do { \
if (!wait_##stat(us)) { \
ps2_error = err; \
goto ERROR; \
} \
} while (0)
uint8_t ps2_error = PS2_ERR_NONE;
static inline uint8_t pbuf_dequeue(void);
static inline void pbuf_enqueue(uint8_t data);
static inline bool pbuf_has_data(void);
static inline void pbuf_clear(void);
void ps2_host_init(void) {
idle();
PS2_INT_INIT();
PS2_INT_ON();
}
uint8_t ps2_host_send(uint8_t data) {
bool parity = true;
ps2_error = PS2_ERR_NONE;
PS2_INT_OFF();
inhibit();
_delay_us(100);
data_lo();
clock_hi();
WAIT(clock_lo, 10000, 10);
for (uint8_t i = 0; i < 8; i++) {
_delay_us(15);
if (data & (1 << i)) {
parity = !parity;
data_hi();
} else {
data_lo();
}
WAIT(clock_hi, 50, 2);
WAIT(clock_lo, 50, 3);
}
_delay_us(15);
if (parity) {
data_hi();
} else {
data_lo();
}
WAIT(clock_hi, 50, 4);
WAIT(clock_lo, 50, 5);
_delay_us(15);
data_hi();
WAIT(data_lo, 50, 6);
WAIT(clock_lo, 50, 7);
WAIT(clock_hi, 50, 8);
WAIT(data_hi, 50, 9);
idle();
PS2_INT_ON();
return ps2_host_recv_response();
ERROR:
idle();
PS2_INT_ON();
return 0;
}
uint8_t ps2_host_recv_response(void) {
uint8_t retry = 25;
while (retry-- && !pbuf_has_data()) {
_delay_ms(1);
}
return pbuf_dequeue();
}
uint8_t ps2_host_recv(void) {
if (pbuf_has_data()) {
ps2_error = PS2_ERR_NONE;
return pbuf_dequeue();
} else {
ps2_error = PS2_ERR_NODATA;
return 0;
}
}
ISR(PS2_INT_VECT) {
static enum {
INIT,
START,
BIT0,
BIT1,
BIT2,
BIT3,
BIT4,
BIT5,
BIT6,
BIT7,
PARITY,
STOP,
} state = INIT;
static uint8_t data = 0;
static uint8_t parity = 1;
if (clock_in()) {
goto RETURN;
}
state++;
switch (state) {
case START:
if (data_in()) goto ERROR;
break;
case BIT0:
case BIT1:
case BIT2:
case BIT3:
case BIT4:
case BIT5:
case BIT6:
case BIT7:
data >>= 1;
if (data_in()) {
data |= 0x80;
parity++;
}
break;
case PARITY:
if (data_in()) {
if (!(parity & 0x01)) goto ERROR;
} else {
if (parity & 0x01) goto ERROR;
}
break;
case STOP:
if (!data_in()) goto ERROR;
pbuf_enqueue(data);
goto DONE;
break;
default:
goto ERROR;
}
goto RETURN;
ERROR:
ps2_error = state;
DONE:
state = INIT;
data = 0;
parity = 1;
RETURN:
return;
}
void ps2_host_set_led(uint8_t led) {
ps2_host_send(0xED);
ps2_host_send(led);
}
#define PBUF_SIZE 32
static uint8_t pbuf[PBUF_SIZE];
static uint8_t pbuf_head = 0;
static uint8_t pbuf_tail = 0;
static inline void pbuf_enqueue(uint8_t data) {
uint8_t sreg = SREG;
cli();
uint8_t next = (pbuf_head + 1) % PBUF_SIZE;
if (next != pbuf_tail) {
pbuf[pbuf_head] = data;
pbuf_head = next;
} else {
print("pbuf: full\n");
}
SREG = sreg;
}
static inline uint8_t pbuf_dequeue(void) {
uint8_t val = 0;
uint8_t sreg = SREG;
cli();
if (pbuf_head != pbuf_tail) {
val = pbuf[pbuf_tail];
pbuf_tail = (pbuf_tail + 1) % PBUF_SIZE;
}
SREG = sreg;
return val;
}
static inline bool pbuf_has_data(void) {
uint8_t sreg = SREG;
cli();
bool has_data = (pbuf_head != pbuf_tail);
SREG = sreg;
return has_data;
}
static inline void pbuf_clear(void) {
uint8_t sreg = SREG;
cli();
pbuf_head = pbuf_tail = 0;
SREG = sreg;
}