442 lines
12 KiB
C
442 lines
12 KiB
C
/*
|
|
* This file is part of the MicroPython project, http://micropython.org/
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Copyright (c) 2017-2018 Scott Shawcroft for Adafruit Industries
|
|
* Copyright (c) 2019 Elvis Pfutzenreuter <epxx@epxx.co>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "common-hal/ps2io/Ps2.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "atmel_start_pins.h"
|
|
#include "hal/include/hal_gpio.h"
|
|
|
|
#include "background.h"
|
|
#include "eic_handler.h"
|
|
#include "mpconfigport.h"
|
|
#include "py/gc.h"
|
|
#include "py/runtime.h"
|
|
#include "samd/external_interrupts.h"
|
|
#include "samd/pins.h"
|
|
#include "shared-bindings/microcontroller/__init__.h"
|
|
#include "shared-bindings/ps2io/Ps2.h"
|
|
#include "supervisor/shared/translate.h"
|
|
|
|
#include "tick.h"
|
|
|
|
#define STATE_IDLE 0
|
|
#define STATE_RECV 1
|
|
#define STATE_RECV_PARITY 2
|
|
#define STATE_RECV_STOP 3
|
|
#define STATE_RECV_ERR 10
|
|
|
|
#define ERROR_STARTBIT 0x01
|
|
#define ERROR_TIMEOUT 0x02
|
|
#define ERROR_PARITY 0x04
|
|
#define ERROR_STOPBIT 0x08
|
|
#define ERROR_BUFFER 0x10
|
|
|
|
#define ERROR_TX_CLKLO 0x100
|
|
#define ERROR_TX_CLKHI 0x200
|
|
#define ERROR_TX_ACKDATA 0x400
|
|
#define ERROR_TX_ACKCLK 0x800
|
|
#define ERROR_TX_RTS 0x1000
|
|
#define ERROR_TX_NORESP 0x2000
|
|
|
|
static void ps2_set_config(ps2io_ps2_obj_t* self) {
|
|
uint32_t sense_setting = EIC_CONFIG_SENSE0_FALL_Val;
|
|
set_eic_handler(self->channel, EIC_HANDLER_PS2);
|
|
turn_on_eic_channel(self->channel, sense_setting);
|
|
}
|
|
|
|
static void disable_interrupt(ps2io_ps2_obj_t* self) {
|
|
uint32_t mask = 1 << self->channel;
|
|
EIC->INTENCLR.reg = mask << EIC_INTENSET_EXTINT_Pos;
|
|
}
|
|
|
|
static void resume_interrupt(ps2io_ps2_obj_t* self) {
|
|
disable_interrupt(self);
|
|
|
|
self->state = STATE_IDLE;
|
|
gpio_set_pin_function(self->clk_pin, GPIO_PIN_FUNCTION_A);
|
|
uint32_t mask = 1 << self->channel;
|
|
EIC->INTFLAG.reg = mask << EIC_INTFLAG_EXTINT_Pos;
|
|
EIC->INTENSET.reg = mask << EIC_INTENSET_EXTINT_Pos;
|
|
|
|
ps2_set_config(self);
|
|
}
|
|
|
|
static void clk_hi(ps2io_ps2_obj_t* self) {
|
|
// External pull-up
|
|
// Must set pull after setting direction.
|
|
gpio_set_pin_direction(self->clk_pin, GPIO_DIRECTION_IN);
|
|
gpio_set_pin_pull_mode(self->clk_pin, GPIO_PULL_OFF);
|
|
}
|
|
|
|
static bool wait_clk_lo(ps2io_ps2_obj_t* self, uint32_t us) {
|
|
clk_hi(self);
|
|
common_hal_mcu_delay_us(1);
|
|
while (gpio_get_pin_level(self->clk_pin) && us) {
|
|
--us;
|
|
common_hal_mcu_delay_us(1);
|
|
}
|
|
return us;
|
|
}
|
|
|
|
static bool wait_clk_hi(ps2io_ps2_obj_t* self, uint32_t us) {
|
|
clk_hi(self);
|
|
common_hal_mcu_delay_us(1);
|
|
while (!gpio_get_pin_level(self->clk_pin) && us) {
|
|
--us;
|
|
common_hal_mcu_delay_us(1);
|
|
}
|
|
return us;
|
|
}
|
|
|
|
static void clk_lo(ps2io_ps2_obj_t* self) {
|
|
gpio_set_pin_pull_mode(self->clk_pin, GPIO_PULL_OFF);
|
|
gpio_set_pin_direction(self->clk_pin, GPIO_DIRECTION_OUT);
|
|
gpio_set_pin_level(self->clk_pin, 0);
|
|
}
|
|
|
|
static void data_hi(ps2io_ps2_obj_t* self) {
|
|
// External pull-up
|
|
gpio_set_pin_direction(self->data_pin, GPIO_DIRECTION_IN);
|
|
gpio_set_pin_pull_mode(self->data_pin, GPIO_PULL_OFF);
|
|
}
|
|
|
|
static bool wait_data_lo(ps2io_ps2_obj_t* self, uint32_t us) {
|
|
data_hi(self);
|
|
common_hal_mcu_delay_us(1);
|
|
while (gpio_get_pin_level(self->data_pin) && us) {
|
|
--us;
|
|
common_hal_mcu_delay_us(1);
|
|
}
|
|
return us;
|
|
}
|
|
|
|
static bool wait_data_hi(ps2io_ps2_obj_t* self, uint32_t us) {
|
|
data_hi(self);
|
|
common_hal_mcu_delay_us(1);
|
|
while (!gpio_get_pin_level(self->data_pin) && us) {
|
|
--us;
|
|
common_hal_mcu_delay_us(1);
|
|
}
|
|
return us;
|
|
}
|
|
|
|
static void data_lo(ps2io_ps2_obj_t* self) {
|
|
gpio_set_pin_pull_mode(self->data_pin, GPIO_PULL_OFF);
|
|
gpio_set_pin_direction(self->data_pin, GPIO_DIRECTION_OUT);
|
|
gpio_set_pin_level(self->data_pin, 0);
|
|
}
|
|
|
|
static void idle(ps2io_ps2_obj_t* self) {
|
|
clk_hi(self);
|
|
data_hi(self);
|
|
}
|
|
|
|
static void inhibit(ps2io_ps2_obj_t* self) {
|
|
clk_lo(self);
|
|
data_hi(self);
|
|
}
|
|
|
|
static void delay_us(uint32_t t) {
|
|
common_hal_mcu_delay_us(t);
|
|
}
|
|
|
|
void ps2_interrupt_handler(uint8_t channel) {
|
|
// Grab the current time first.
|
|
uint32_t current_us;
|
|
uint64_t current_ms;
|
|
current_tick(¤t_ms, ¤t_us);
|
|
|
|
ps2io_ps2_obj_t* self = get_eic_channel_data(channel);
|
|
int data_bit = gpio_get_pin_level(self->data_pin) ? 1 : 0;
|
|
|
|
// test for timeout
|
|
if (self->state != STATE_IDLE) {
|
|
int64_t diff_ms = current_ms - self->last_int_ms;
|
|
if (diff_ms >= 2) { // a.k.a. > 1.001ms
|
|
self->last_errors |= ERROR_TIMEOUT;
|
|
self->state = STATE_IDLE;
|
|
}
|
|
}
|
|
|
|
self->last_int_us = current_us;
|
|
self->last_int_ms = current_ms;
|
|
|
|
if (self->state == STATE_IDLE) {
|
|
self->bits = 0;
|
|
self->parity = false;
|
|
self->bitcount = 0;
|
|
self->state = STATE_RECV;
|
|
if (data_bit) {
|
|
// start bit should be 0
|
|
self->last_errors |= ERROR_STARTBIT;
|
|
self->state = STATE_RECV_ERR;
|
|
} else {
|
|
self->state = STATE_RECV;
|
|
}
|
|
|
|
} else if (self->state == STATE_RECV) {
|
|
if (data_bit) {
|
|
self->bits |= data_bit << self->bitcount;
|
|
self->parity = !self->parity;
|
|
}
|
|
++self->bitcount;
|
|
if (self->bitcount >= 8) {
|
|
self->state = STATE_RECV_PARITY;
|
|
}
|
|
|
|
} else if (self->state == STATE_RECV_PARITY) {
|
|
++self->bitcount;
|
|
if (data_bit) {
|
|
self->parity = !self->parity;
|
|
}
|
|
if (!self->parity) {
|
|
self->last_errors |= ERROR_PARITY;
|
|
self->state = STATE_RECV_ERR;
|
|
} else {
|
|
self->state = STATE_RECV_STOP;
|
|
}
|
|
|
|
} else if (self->state == STATE_RECV_STOP) {
|
|
++self->bitcount;
|
|
if (! data_bit) {
|
|
self->last_errors |= ERROR_STOPBIT;
|
|
} else if (self->waiting_cmd_response) {
|
|
self->cmd_response = self->bits;
|
|
self->waiting_cmd_response = false;
|
|
} else if (self->bufcount >= sizeof(self->buffer)) {
|
|
self->last_errors |= ERROR_BUFFER;
|
|
} else {
|
|
self->buffer[self->bufposw] = self->bits;
|
|
self->bufposw = (self->bufposw + 1) % sizeof(self->buffer);
|
|
self->bufcount++;
|
|
}
|
|
self->state = STATE_IDLE;
|
|
|
|
} else if (self->state == STATE_RECV_ERR) {
|
|
// just count the bits until idle
|
|
if (++self->bitcount >= 10) {
|
|
self->state = STATE_IDLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void common_hal_ps2io_ps2_construct(ps2io_ps2_obj_t* self,
|
|
const mcu_pin_obj_t* data_pin, const mcu_pin_obj_t* clk_pin) {
|
|
if (!clk_pin->has_extint) {
|
|
mp_raise_RuntimeError(translate("No hardware support on clk pin"));
|
|
}
|
|
if (eic_get_enable() && !eic_channel_free(clk_pin->extint_channel)) {
|
|
mp_raise_RuntimeError(translate("EXTINT channel already in use"));
|
|
}
|
|
|
|
clk_hi(self);
|
|
data_hi(self);
|
|
|
|
self->channel = clk_pin->extint_channel;
|
|
self->clk_pin = clk_pin->number;
|
|
self->data_pin = data_pin->number;
|
|
self->state = STATE_IDLE;
|
|
self->bufcount = 0;
|
|
self->bufposr = 0;
|
|
self->bufposw = 0;
|
|
self->waiting_cmd_response = false;
|
|
|
|
set_eic_channel_data(clk_pin->extint_channel, (void*) self);
|
|
|
|
// Check to see if the EIC is enabled and start it up if its not.'
|
|
if (eic_get_enable() == 0) {
|
|
turn_on_external_interrupt_controller();
|
|
}
|
|
|
|
gpio_set_pin_function(clk_pin->number, GPIO_PIN_FUNCTION_A);
|
|
gpio_set_pin_function(data_pin->number, GPIO_PIN_FUNCTION_A);
|
|
|
|
turn_on_cpu_interrupt(self->channel);
|
|
|
|
claim_pin(clk_pin);
|
|
claim_pin(data_pin);
|
|
|
|
// Set config will enable the EIC.
|
|
ps2_set_config(self);
|
|
}
|
|
|
|
bool common_hal_ps2io_ps2_deinited(ps2io_ps2_obj_t* self) {
|
|
return self->clk_pin == NO_PIN;
|
|
}
|
|
|
|
void common_hal_ps2io_ps2_deinit(ps2io_ps2_obj_t* self) {
|
|
if (common_hal_ps2io_ps2_deinited(self)) {
|
|
return;
|
|
}
|
|
set_eic_handler(self->channel, EIC_HANDLER_NO_INTERRUPT);
|
|
turn_off_eic_channel(self->channel);
|
|
reset_pin_number(self->clk_pin);
|
|
reset_pin_number(self->data_pin);
|
|
self->clk_pin = NO_PIN;
|
|
self->data_pin = NO_PIN;
|
|
}
|
|
|
|
uint16_t common_hal_ps2io_ps2_get_len(ps2io_ps2_obj_t* self) {
|
|
return self->bufcount;
|
|
}
|
|
|
|
bool common_hal_ps2io_ps2_get_paused(ps2io_ps2_obj_t* self) {
|
|
uint32_t mask = 1 << self->channel;
|
|
return (EIC->INTENSET.reg & (mask << EIC_INTENSET_EXTINT_Pos)) == 0;
|
|
}
|
|
|
|
int16_t common_hal_ps2io_ps2_popleft(ps2io_ps2_obj_t* self)
|
|
{
|
|
common_hal_mcu_disable_interrupts();
|
|
if (self->bufcount <= 0) {
|
|
common_hal_mcu_enable_interrupts();
|
|
return -1;
|
|
}
|
|
uint8_t b = self->buffer[self->bufposr];
|
|
self->bufposr = (self->bufposr + 1) % sizeof(self->buffer);
|
|
self->bufcount -= 1;
|
|
common_hal_mcu_enable_interrupts();
|
|
return b;
|
|
}
|
|
|
|
uint16_t common_hal_ps2io_ps2_clear_errors(ps2io_ps2_obj_t* self)
|
|
{
|
|
common_hal_mcu_disable_interrupts();
|
|
uint16_t errors = self->last_errors;
|
|
self->last_errors = 0;
|
|
common_hal_mcu_enable_interrupts();
|
|
return errors;
|
|
}
|
|
|
|
// Based upon TMK implementation of PS/2 protocol
|
|
// https://github.com/tmk/tmk_keyboard/blob/master/tmk_core/protocol/ps2_interrupt.c
|
|
|
|
int16_t common_hal_ps2io_ps2_sendcmd(ps2io_ps2_obj_t* self, uint8_t b)
|
|
{
|
|
disable_interrupt(self);
|
|
inhibit(self);
|
|
delay_us(100);
|
|
|
|
/* RTS and start bit */
|
|
data_lo(self);
|
|
clk_hi(self);
|
|
if (!wait_clk_lo(self, 10000)) {
|
|
self->last_errors |= ERROR_TX_RTS;
|
|
goto ERROR;
|
|
}
|
|
|
|
bool parity = true;
|
|
for (uint8_t i = 0; i < 8; i++) {
|
|
delay_us(15);
|
|
if (b & (1 << i)) {
|
|
parity = !parity;
|
|
data_hi(self);
|
|
} else {
|
|
data_lo(self);
|
|
}
|
|
if (!wait_clk_hi(self, 50)) {
|
|
self->last_errors |= ERROR_TX_CLKHI;
|
|
goto ERROR;
|
|
}
|
|
if (!wait_clk_lo(self, 50)) {
|
|
self->last_errors |= ERROR_TX_CLKLO;
|
|
goto ERROR;
|
|
}
|
|
}
|
|
|
|
delay_us(15);
|
|
if (parity) {
|
|
data_hi(self);
|
|
} else {
|
|
data_lo(self);
|
|
}
|
|
if (!wait_clk_hi(self, 50)) {
|
|
self->last_errors |= ERROR_TX_CLKHI;
|
|
goto ERROR;
|
|
}
|
|
if (!wait_clk_lo(self, 50)) {
|
|
self->last_errors |= ERROR_TX_CLKLO;
|
|
goto ERROR;
|
|
}
|
|
|
|
/* Stop bit */
|
|
delay_us(15);
|
|
data_hi(self);
|
|
|
|
/* Ack */
|
|
if (!wait_data_lo(self, 50)) {
|
|
self->last_errors |= ERROR_TX_ACKDATA;
|
|
goto ERROR;
|
|
}
|
|
if (!wait_clk_lo(self, 50)) {
|
|
self->last_errors |= ERROR_TX_ACKCLK;
|
|
goto ERROR;
|
|
}
|
|
|
|
/* wait for idle state */
|
|
if (!wait_clk_hi(self, 50)) {
|
|
self->last_errors |= ERROR_TX_ACKCLK;
|
|
goto ERROR;
|
|
}
|
|
if (!wait_data_hi(self, 50)) {
|
|
self->last_errors |= ERROR_TX_ACKDATA;
|
|
goto ERROR;
|
|
}
|
|
|
|
/* Wait for response byte */
|
|
self->waiting_cmd_response = true;
|
|
idle(self);
|
|
resume_interrupt(self);
|
|
|
|
for (int i = 0; i < 25; ++i) {
|
|
delay_us(1000);
|
|
common_hal_mcu_disable_interrupts();
|
|
bool has_response = !self->waiting_cmd_response;
|
|
uint8_t response = self->cmd_response;
|
|
common_hal_mcu_enable_interrupts();
|
|
|
|
if (has_response) {
|
|
return response;
|
|
}
|
|
}
|
|
|
|
/* No response */
|
|
common_hal_mcu_disable_interrupts();
|
|
self->waiting_cmd_response = false;
|
|
self->last_errors |= ERROR_TX_NORESP;
|
|
common_hal_mcu_enable_interrupts();
|
|
return -1;
|
|
|
|
/* Other errors */
|
|
ERROR:
|
|
idle(self);
|
|
resume_interrupt(self);
|
|
return -1;
|
|
}
|