Files
USB4VC/firmware/ibmpc/Src/ps2mouse.c
2022-05-13 14:17:19 +01:00

415 lines
11 KiB
C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "helpers.h"
#include "shared.h"
#include "ps2mouse.h"
#include "delay_us.h"
// https://wiki.osdev.org/PS/2_Mouse
#define CLKHALF 18
#define CLKFULL 36
#define BYTEWAIT 700
#define BYTEWAIT_END 250
#define PS2MOUSE_BUS_TIMEOUT_MS 30
#define CODE_UNUSED 0xff
#define PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS 20
#define PS2MOUSE_MODE_STREAM 0
#define PS2MOUSE_MODE_REMOTE 1
#define PS2MOUSE_MODE_WRAP 2
GPIO_TypeDef* ps2mouse_clk_port;
uint16_t ps2mouse_clk_pin;
GPIO_TypeDef* ps2mouse_data_port;
uint16_t ps2mouse_data_pin;
uint8_t ps2mouse_data_reporting_enabled;
uint8_t ps2mouse_sampling_rate;
uint8_t ps2mouse_resolution;
uint8_t ps2mouse_scale;
#define SAMPLE_RATE_HISTORY_BUF_SIZE 8
uint8_t sample_rate_history[SAMPLE_RATE_HISTORY_BUF_SIZE];
uint8_t sample_rate_history_index;
uint8_t mouse_device_id;
uint8_t ps2mouse_current_mode;
uint8_t x_accumulator, y_accumulator, scroll_accumulator;
#define PS2MOUSE_PACKET_SIZE_GENERIC 3
#define PS2MOUSE_PACKET_SIZE_INTELLIMOUSE 4
#define PS2MOUSE_CLK_HI() HAL_GPIO_WritePin(ps2mouse_clk_port, ps2mouse_clk_pin, GPIO_PIN_SET)
#define PS2MOUSE_CLK_LOW() HAL_GPIO_WritePin(ps2mouse_clk_port, ps2mouse_clk_pin, GPIO_PIN_RESET)
#define PS2MOUSE_DATA_HI() HAL_GPIO_WritePin(ps2mouse_data_port, ps2mouse_data_pin, GPIO_PIN_SET)
#define PS2MOUSE_DATA_LOW() HAL_GPIO_WritePin(ps2mouse_data_port, ps2mouse_data_pin, GPIO_PIN_RESET)
#define PS2MOUSE_READ_DATA_PIN() HAL_GPIO_ReadPin(ps2mouse_data_port, ps2mouse_data_pin)
#define PS2MOUSE_READ_CLK_PIN() HAL_GPIO_ReadPin(ps2mouse_clk_port, ps2mouse_clk_pin)
#define PS2MOUSE_SENDACK() ps2mouse_write_delay_start(0xFA, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS)
void ps2mouse_release_lines(void)
{
PS2MOUSE_CLK_HI();
PS2MOUSE_DATA_HI();
}
void ps2mouse_restore_defaults(void)
{
ps2mouse_sampling_rate = 100;
ps2mouse_resolution = 2;
ps2mouse_scale = 1;
ps2mouse_data_reporting_enabled = 0;
ps2mouse_current_mode = PS2MOUSE_MODE_STREAM;
}
void ps2mouse_reset(void)
{
ps2mouse_restore_defaults();
sample_rate_history_index = 0;
memset(sample_rate_history, 0, SAMPLE_RATE_HISTORY_BUF_SIZE);
mouse_device_id = 0;
}
void ps2mouse_send_bat(uint8_t timeout_ms)
{
if(ps2mouse_wait_for_idle(timeout_ms))
return;
ps2mouse_write(0xaa, 100);
ps2mouse_write(0, 100);
}
void ps2mouse_init(GPIO_TypeDef* clk_port, uint16_t clk_pin, GPIO_TypeDef* data_port, uint16_t data_pin)
{
ps2mouse_clk_port = clk_port;
ps2mouse_clk_pin = clk_pin;
ps2mouse_data_port = data_port;
ps2mouse_data_pin = data_pin;
ps2mouse_reset();
ps2mouse_release_lines();
}
uint8_t ps2mouse_get_bus_status(void)
{
if(PS2MOUSE_READ_DATA_PIN() == GPIO_PIN_SET && PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_SET)
return PS2_BUS_IDLE;
if(PS2MOUSE_READ_DATA_PIN() == GPIO_PIN_SET && PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_RESET)
return PS2_BUS_INHIBIT;
if(PS2MOUSE_READ_DATA_PIN() == GPIO_PIN_RESET && PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_SET)
return PS2_BUS_REQ_TO_SEND;
return PS2_BUS_UNKNOWN;
}
uint8_t ps2mouse_read(uint8_t* result, uint8_t timeout_ms)
{
uint16_t data = 0x00;
uint16_t bit = 0x01;
uint32_t ps2mouse_wait_start = HAL_GetTick();
while(ps2mouse_get_bus_status() != PS2_BUS_REQ_TO_SEND)
{
if(HAL_GetTick() - ps2mouse_wait_start >= timeout_ms)
return PS2_ERROR_TIMEOUT;
}
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
while (bit < 0x0100)
{
if (PS2MOUSE_READ_DATA_PIN() == GPIO_PIN_SET)
data = data | bit;
bit = bit << 1;
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
}
// stop bit
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
delay_us(CLKHALF);
PS2MOUSE_DATA_LOW();
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
PS2MOUSE_DATA_HI();
*result = data & 0xFF;
return PS2_OK;
}
uint8_t ps2mouse_wait_for_idle(uint8_t timeout_ms)
{
ps2mouse_idle_check:
uint32_t ps2mouse_wait_start = HAL_GetTick();
while(ps2mouse_get_bus_status() != PS2_BUS_IDLE)
{
if(HAL_GetTick() - ps2mouse_wait_start >= timeout_ms)
return PS2_ERROR_TIMEOUT;
}
ps2mouse_wait_start = micros();
// make sure idle is more than 50us
while(micros() - ps2mouse_wait_start < 60)
{
if(ps2mouse_get_bus_status() != PS2_BUS_IDLE)
goto ps2mouse_idle_check;
}
return PS2_OK;
}
uint8_t ps2mouse_write_delay_start(uint8_t data, uint8_t timeout_ms)
{
if(ps2mouse_wait_for_idle(timeout_ms) != 0)
return PS2_ERROR_TIMEOUT;
delay_us(BYTEWAIT);
return ps2mouse_write_nowait(data);
}
uint8_t ps2mouse_write(uint8_t data, uint8_t timeout_ms)
{
if(ps2mouse_wait_for_idle(timeout_ms) != 0)
return PS2_ERROR_TIMEOUT;
return ps2mouse_write_nowait(data);
}
void ps2mouse_host_req_reply(uint8_t cmd, mouse_event* mevent)
{
uint8_t first_byte = 0;
if(cmd == 0xFF) // reset
{
ps2mouse_reset();
PS2MOUSE_SENDACK();
ps2mouse_write(0xAA, 250);
ps2mouse_write(0, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS);
return;
}
switch (cmd)
{
case 0xFE: //resend
PS2MOUSE_SENDACK();
break;
case 0xF6: //set defaults
ps2mouse_restore_defaults();
PS2MOUSE_SENDACK();
break;
case 0xF5: //disable data reporting
ps2mouse_data_reporting_enabled = 0;
PS2MOUSE_SENDACK();
break;
case 0xF4: //enable data reporting
ps2mouse_data_reporting_enabled = 1;
PS2MOUSE_SENDACK();
break;
case 0xF3: //set sampling rate
PS2MOUSE_SENDACK();
if(ps2mouse_read(&ps2mouse_sampling_rate, 150) == 0)
{
sample_rate_history[sample_rate_history_index] = ps2mouse_sampling_rate;
if(sample_rate_history_index < SAMPLE_RATE_HISTORY_BUF_SIZE-1)
sample_rate_history_index++;
PS2MOUSE_SENDACK();
}
break;
case 0xF2: //get device id
PS2MOUSE_SENDACK();
if (sample_rate_history_index > 2 && sample_rate_history[sample_rate_history_index-1] == 80 && sample_rate_history[sample_rate_history_index-2] == 100 && sample_rate_history[sample_rate_history_index-3] == 200)
mouse_device_id = 3; // intellimouse with scroll wheel
if (sample_rate_history_index > 2 && sample_rate_history[sample_rate_history_index-1] == 80 && sample_rate_history[sample_rate_history_index-2] == 200 && sample_rate_history[sample_rate_history_index-3] == 200)
mouse_device_id = 4; // intellimouse 5-button scrolling mouse
ps2mouse_write(mouse_device_id, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS);
break;
case 0xF0: // set remote mode
PS2MOUSE_SENDACK();
ps2mouse_current_mode = PS2MOUSE_MODE_REMOTE;
break;
case 0xEE: // set wrap mode
PS2MOUSE_SENDACK();
ps2mouse_current_mode = PS2MOUSE_MODE_WRAP;
break;
case 0xEB: // read data
PS2MOUSE_SENDACK();
break;
case 0xEA: // set stream mode
ps2mouse_current_mode = PS2MOUSE_MODE_STREAM;
PS2MOUSE_SENDACK();
break;
case 0xE9: // status request
PS2MOUSE_SENDACK();
if(ps2mouse_current_mode == PS2MOUSE_MODE_REMOTE)
first_byte |= 0x40;
if(ps2mouse_data_reporting_enabled)
first_byte |= 0x20;
if(ps2mouse_scale == 2)
first_byte |= 0x10;
if(mevent->button_left)
first_byte |= 0x4;
if(mevent->button_middle)
first_byte |= 0x2;
if(mevent->button_right)
first_byte |= 0x1;
ps2mouse_write(first_byte, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS);
ps2mouse_write(ps2mouse_resolution, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS);
ps2mouse_write(ps2mouse_sampling_rate, PS2MOUSE_WRITE_DEFAULT_TIMEOUT_MS);
break;
case 0xE8: // set resolution
PS2MOUSE_SENDACK();
if(ps2mouse_read(&ps2mouse_resolution, 150) == 0)
PS2MOUSE_SENDACK();
break;
case 0xE6: // reset scale
PS2MOUSE_SENDACK();
ps2mouse_scale = 1;
break;
case 0xE7: // set scale 2 to 1
PS2MOUSE_SENDACK();
ps2mouse_scale = 2;
break;
default:
PS2MOUSE_SENDACK();
}
}
uint8_t ps2mouse_get_outgoing_data(mouse_event* this_event, ps2_outgoing_buf* pbuf)
{
if(ps2mouse_data_reporting_enabled == 0)
return PS2_ERROR_REPORTING_DISABLED;
if(ps2mouse_current_mode != PS2MOUSE_MODE_STREAM)
return PS2_ERROR_UNIMPLEMENTED_MODE;
memset(pbuf->data, 0, PS2_OUT_BUF_MAXSIZE);
pbuf->size = PS2MOUSE_PACKET_SIZE_GENERIC;
pbuf->data[0] = 0x8; // bit 3 is always 1
// https://wiki.osdev.org/PS/2_Mouse
if(this_event->button_left)
pbuf->data[0] = pbuf->data[0] | 0x1;
if(this_event->button_right)
pbuf->data[0] = pbuf->data[0] | 0x2;
if(this_event->button_middle)
pbuf->data[0] = pbuf->data[0] | 0x4;
if(this_event->movement_x < 0)
pbuf->data[0] = pbuf->data[0] | 0x10;
if(this_event->movement_y < 0)
pbuf->data[0] = pbuf->data[0] | 0x20;
pbuf->data[1] = (uint8_t)(this_event->movement_x);
pbuf->data[2] = (uint8_t)(this_event->movement_y);
if(mouse_device_id == 3) // 3 button intellimouse with 1 scroll wheel
{
pbuf->data[3] = (uint8_t)(this_event->scroll_vertical);
}
else if(mouse_device_id == 4) // 5 button intellimouse with 2 scroll wheels
{
if(this_event->button_side)
pbuf->data[3] = pbuf->data[3] | 0x10;
if(this_event->button_extra)
pbuf->data[3] = pbuf->data[3] | 0x20;
if(this_event->scroll_horizontal > 0)
pbuf->data[3] = pbuf->data[3] | 0x2;
else if(this_event->scroll_horizontal < 0)
pbuf->data[3] = pbuf->data[3] | 0xe;
if(this_event->scroll_vertical > 0)
pbuf->data[3] = pbuf->data[3] | 0x1;
else if(this_event->scroll_vertical < 0)
pbuf->data[3] = pbuf->data[3] | 0xf;
}
if(mouse_device_id != 0)
pbuf->size = PS2MOUSE_PACKET_SIZE_INTELLIMOUSE;
return PS2_OK;
}
uint8_t ps2mouse_write_nowait(uint8_t data)
{
uint8_t parity = 1;
PS2MOUSE_DATA_LOW();
delay_us(CLKHALF);
// device sends on falling clock
PS2MOUSE_CLK_LOW(); // start bit
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
if(PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_RESET)
{
ps2mouse_release_lines();
return PS2_ERROR_HOST_INHIBIT;
}
for (int i=0; i < 8; i++)
{
if (data & 0x01)
PS2MOUSE_DATA_HI();
else
PS2MOUSE_DATA_LOW();
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
if(PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_RESET)
{
ps2mouse_release_lines();
return PS2_ERROR_HOST_INHIBIT;
}
parity = parity ^ (data & 0x01);
data = data >> 1;
}
// parity bit
if (parity)
PS2MOUSE_DATA_HI();
else
PS2MOUSE_DATA_LOW();
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
if(PS2MOUSE_READ_CLK_PIN() == GPIO_PIN_RESET)
{
ps2mouse_release_lines();
return PS2_ERROR_HOST_INHIBIT;
}
// stop bit
PS2MOUSE_DATA_HI();
delay_us(CLKHALF);
PS2MOUSE_CLK_LOW();
delay_us(CLKFULL);
PS2MOUSE_CLK_HI();
delay_us(CLKHALF);
delay_us(BYTEWAIT_END);
return PS2_OK;
}
uint8_t ps2mouse_send_update(ps2_outgoing_buf* pbuf)
{
uint8_t write_result;
for (int i = 0; i < pbuf->size; ++i)
{
// return error if inhibited or interrupted while transmitting
write_result = ps2mouse_write(pbuf->data[i], 200);
if(write_result)
return write_result;
}
return PS2_OK;
}