-/*
+/*
* Copyright 2012 Jun Wako <wakojun@gmail.com>
* This file is based on:
* LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse
#include "action.h"
#include "led.h"
#include "sendchar.h"
+#include "ringbuf.h"
#include "debug.h"
#ifdef SLEEP_LED_ENABLE
#include "sleep_led.h"
#endif
#include "suspend.h"
+#include "hook.h"
+#include "timer.h"
+
+#ifdef TMK_LUFA_DEBUG_SUART
+#include "avr/suart.h"
+#endif
+
+#ifdef TMK_LUFA_DEBUG_UART
+#include "uart.h"
+#endif
+#include "matrix.h"
#include "descriptor.h"
#include "lufa.h"
+
+//#define TMK_LUFA_DEBUG
+
+
uint8_t keyboard_idle = 0;
+/* 0: Boot Protocol, 1: Report Protocol(default) */
uint8_t keyboard_protocol = 1;
static uint8_t keyboard_led_stats = 0;
* Console
******************************************************************************/
#ifdef CONSOLE_ENABLE
-static void Console_Task(void)
+#define SENDBUF_SIZE 256
+static uint8_t sbuf[SENDBUF_SIZE];
+static ringbuf_t sendbuf = {
+ .buffer = sbuf,
+ .head = 0,
+ .tail = 0,
+ .size_mask = SENDBUF_SIZE - 1
+};
+
+// TODO: Around 2500ms delay often works anyhoo but proper startup would be better
+// 1000ms delay of hid_listen affects this probably
+/* wait for Console startup */
+static bool console_is_ready(void)
{
- /* Device must be connected and configured for the task to run */
- if (USB_DeviceState != DEVICE_STATE_Configured)
- return;
+ static bool hid_listen_ready = false;
+ if (!hid_listen_ready) {
+ if (timer_read32() < 2500)
+ return false;
+ hid_listen_ready = true;
+ }
+ return true;
+}
+
+static bool console_putc(uint8_t c)
+{
+ // return immediately if called while interrupt
+ if (!(SREG & (1<<SREG_I)))
+ goto EXIT;
+
+ if (USB_DeviceState != DEVICE_STATE_Configured && !ringbuf_is_full(&sendbuf))
+ goto EXIT;
+
+ if (!console_is_ready() && !ringbuf_is_full(&sendbuf))
+ goto EXIT;
+
+ /* Data lost considerations:
+ * 1. When buffer is full at early satage of startup, we will have to start sending
+ * before console_is_ready() returns true. Data can be lost even if sending data
+ * seems to be successful on USB. hid_listen on host is not ready perhaps?
+ * Sometime first few packets are lost when buffer is full at startup.
+ * 2. When buffer is full and USB pipe is not ready, new coming data will be lost.
+ * 3. console_task() cannot send data in buffer while main loop is blocked.
+ */
+ /* retry timeout */
+ const uint8_t CONSOLE_TIMOUT = 5; // 1 is too small, 2 seems to be enough for Linux
+ static uint8_t timeout = CONSOLE_TIMOUT;
+ uint16_t prev = timer_read();
+ bool done = false;
uint8_t ep = Endpoint_GetCurrentEndpoint();
+ Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM);
-#if 0
- // TODO: impl receivechar()/recvchar()
- Endpoint_SelectEndpoint(CONSOLE_OUT_EPNUM);
+AGAIN:
+ if (Endpoint_IsStalled() || !Endpoint_IsEnabled() || !Endpoint_IsConfigured()) {
+ goto EXIT_RESTORE_EP;
+ }
- /* Check to see if a packet has been sent from the host */
- if (Endpoint_IsOUTReceived())
- {
- /* Check to see if the packet contains data */
- if (Endpoint_IsReadWriteAllowed())
- {
- /* Create a temporary buffer to hold the read in report from the host */
- uint8_t ConsoleData[CONSOLE_EPSIZE];
-
- /* Read Console Report Data */
- Endpoint_Read_Stream_LE(&ConsoleData, sizeof(ConsoleData), NULL);
-
- /* Process Console Report Data */
- //ProcessConsoleHIDReport(ConsoleData);
+ // write from buffer to endpoint bank
+ while (!ringbuf_is_empty(&sendbuf) && Endpoint_IsReadWriteAllowed()) {
+ Endpoint_Write_8(ringbuf_get(&sendbuf));
+
+ // clear bank when it is full
+ if (!Endpoint_IsReadWriteAllowed() && Endpoint_IsINReady()) {
+ Endpoint_ClearIN();
+ timeout = CONSOLE_TIMOUT; // re-enable retry only when host can receive
}
+ }
- /* Finalize the stream transfer to send the last packet */
- Endpoint_ClearOUT();
+ // write c to bank directly if there is no others in buffer
+ if (ringbuf_is_empty(&sendbuf) && Endpoint_IsReadWriteAllowed()) {
+ Endpoint_Write_8(c);
+ done = true;
+ }
+
+ // clear bank when there are chars in bank
+ if (Endpoint_BytesInEndpoint() && Endpoint_IsINReady()) {
+ // Windows needs to fill packet with 0
+ while (Endpoint_IsReadWriteAllowed()) {
+ Endpoint_Write_8(0);
+ }
+ Endpoint_ClearIN();
+ timeout = CONSOLE_TIMOUT; // re-enable retry only when host can receive
+ }
+
+ if (done) {
+ Endpoint_SelectEndpoint(ep);
+ return true;
}
-#endif
- /* IN packet */
+ /* retry when buffer is full.
+ * once timeout this is disabled until host receives actually,
+ * otherwise this will block or make main loop execution sluggish.
+ */
+ if (ringbuf_is_full(&sendbuf) && timeout) {
+ uint16_t curr = timer_read();
+ if (curr != prev) {
+ timeout--;
+ prev = curr;
+ }
+ goto AGAIN;
+ }
+
+EXIT_RESTORE_EP:
+ Endpoint_SelectEndpoint(ep);
+EXIT:
+ return ringbuf_put(&sendbuf, c);
+}
+
+static void console_flush(void)
+{
+ if (!console_is_ready())
+ return;
+
+ if (USB_DeviceState != DEVICE_STATE_Configured)
+ return;
+
+ uint8_t ep = Endpoint_GetCurrentEndpoint();
+
Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM);
if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) {
Endpoint_SelectEndpoint(ep);
return;
}
- // fill empty bank
- while (Endpoint_IsReadWriteAllowed())
- Endpoint_Write_8(0);
+ // write from buffer to endpoint bank
+ while (!ringbuf_is_empty(&sendbuf) && Endpoint_IsReadWriteAllowed()) {
+ Endpoint_Write_8(ringbuf_get(&sendbuf));
+
+ // clear bank when it is full
+ if (!Endpoint_IsReadWriteAllowed() && Endpoint_IsINReady()) {
+ Endpoint_ClearIN();
+ }
+ }
- // flash senchar packet
- if (Endpoint_IsINReady()) {
+ // clear bank when there are chars in bank
+ if (Endpoint_BytesInEndpoint() && Endpoint_IsINReady()) {
+ // Windows needs to fill packet with 0
+ while (Endpoint_IsReadWriteAllowed()) {
+ Endpoint_Write_8(0);
+ }
Endpoint_ClearIN();
}
Endpoint_SelectEndpoint(ep);
}
-#else
-static void Console_Task(void)
+
+static void console_task(void)
{
+ static uint16_t fn = 0;
+ if (fn == USB_Device_GetFrameNumber()) {
+ return;
+ }
+ fn = USB_Device_GetFrameNumber();
+ console_flush();
}
#endif
*/
void EVENT_USB_Device_Connect(void)
{
+#ifdef TMK_LUFA_DEBUG
print("[C]");
+#endif
/* For battery powered device */
if (!USB_IsInitialized) {
USB_Disable();
USB_Init();
- USB_Device_EnableSOFEvents();
}
}
void EVENT_USB_Device_Disconnect(void)
{
+#ifdef TMK_LUFA_DEBUG
print("[D]");
+#endif
/* For battery powered device */
USB_IsInitialized = false;
-/* TODO: This doesn't work. After several plug in/outs can not be enumerated.
+/* TODO: This doesn't work. After several plug in/outs can not be enumerated.
if (USB_IsInitialized) {
USB_Disable(); // Disable all interrupts
USB_Controller_Enable();
void EVENT_USB_Device_Reset(void)
{
+#ifdef TMK_LUFA_DEBUG
print("[R]");
+#endif
}
void EVENT_USB_Device_Suspend()
{
+#ifdef TMK_LUFA_DEBUG
print("[S]");
- matrix_power_down();
-#ifdef SLEEP_LED_ENABLE
- sleep_led_enable();
#endif
+ hook_usb_suspend_entry();
}
void EVENT_USB_Device_WakeUp()
{
+#ifdef TMK_LUFA_DEBUG
print("[W]");
- suspend_wakeup_init();
-
-#ifdef SLEEP_LED_ENABLE
- sleep_led_disable();
- // NOTE: converters may not accept this
- led_set(host_keyboard_leds());
#endif
-}
-
-void EVENT_USB_Device_StartOfFrame(void)
-{
- Console_Task();
+ hook_usb_wakeup();
}
/** Event handler for the USB_ConfigurationChanged event.
* This is fired when the host sets the current configuration of the USB device after enumeration.
+ *
+ * ATMega32u2 supports dual bank(ping-pong mode) only on endpoint 3 and 4,
+ * it is safe to use singl bank for all endpoints.
*/
void EVENT_USB_Device_ConfigurationChanged(void)
{
+#ifdef TMK_LUFA_DEBUG
+ print("[c]");
+#endif
bool ConfigSuccess = true;
/* Setup Keyboard HID Report Endpoints */
#ifdef CONSOLE_ENABLE
/* Setup Console HID Report Endpoints */
ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
- CONSOLE_EPSIZE, ENDPOINT_BANK_DOUBLE);
+ CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE);
#if 0
ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_OUT_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_OUT,
CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE);
/* Write the report data to the control endpoint */
Endpoint_Write_Control_Stream_LE(ReportData, ReportSize);
Endpoint_ClearOUT();
+#ifdef TMK_LUFA_DEBUG
+ xprintf("[r%d]", USB_ControlRequest.wIndex);
+#endif
}
break;
Endpoint_ClearOUT();
Endpoint_ClearStatusStage();
+#ifdef TMK_LUFA_DEBUG
+ xprintf("[L%d]", USB_ControlRequest.wIndex);
+#endif
break;
}
Endpoint_Write_8(keyboard_protocol);
Endpoint_ClearIN();
Endpoint_ClearStatusStage();
+#ifdef TMK_LUFA_DEBUG
+ print("[p]");
+#endif
}
}
Endpoint_ClearSETUP();
Endpoint_ClearStatusStage();
- keyboard_protocol = ((USB_ControlRequest.wValue & 0xFF) != 0x00);
-#ifdef NKRO_ENABLE
- keyboard_nkro = !!keyboard_protocol;
-#endif
+ keyboard_protocol = (USB_ControlRequest.wValue & 0xFF);
clear_keyboard();
+#ifdef TMK_LUFA_DEBUG
+ print("[P]");
+#endif
}
}
Endpoint_ClearStatusStage();
keyboard_idle = ((USB_ControlRequest.wValue & 0xFF00) >> 8);
+#ifdef TMK_LUFA_DEBUG
+ xprintf("[I%d]%d", USB_ControlRequest.wIndex, (USB_ControlRequest.wValue & 0xFF00) >> 8);
+#endif
}
break;
Endpoint_Write_8(keyboard_idle);
Endpoint_ClearIN();
Endpoint_ClearStatusStage();
+#ifdef TMK_LUFA_DEBUG
+ print("[i]");
+#endif
}
break;
}
/*******************************************************************************
- * Host driver
+ * Host driver
******************************************************************************/
static uint8_t keyboard_leds(void)
{
static void send_keyboard(report_keyboard_t *report)
{
- uint8_t timeout = 255;
+ uint8_t timeout = 128;
if (USB_DeviceState != DEVICE_STATE_Configured)
return;
/* Select the Keyboard Report Endpoint */
#ifdef NKRO_ENABLE
- if (keyboard_nkro) {
+ if (keyboard_protocol && keyboard_nkro) {
/* Report protocol - NKRO */
Endpoint_SelectEndpoint(NKRO_IN_EPNUM);
/* Check if write ready for a polling interval around 1ms */
- while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(4);
+ while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(8);
if (!Endpoint_IsReadWriteAllowed()) return;
/* Write Keyboard Report Data */
Endpoint_SelectEndpoint(KEYBOARD_IN_EPNUM);
/* Check if write ready for a polling interval around 10ms */
- while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40);
+ while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(80);
if (!Endpoint_IsReadWriteAllowed()) return;
/* Write Keyboard Report Data */
static void send_system(uint16_t data)
{
+#ifdef EXTRAKEY_ENABLE
uint8_t timeout = 255;
if (USB_DeviceState != DEVICE_STATE_Configured)
report_extra_t r = {
.report_id = REPORT_ID_SYSTEM,
- .usage = data
+ .usage = data - SYSTEM_POWER_DOWN + 1
};
Endpoint_SelectEndpoint(EXTRAKEY_IN_EPNUM);
Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL);
Endpoint_ClearIN();
+#endif
}
static void send_consumer(uint16_t data)
{
+#ifdef EXTRAKEY_ENABLE
uint8_t timeout = 255;
if (USB_DeviceState != DEVICE_STATE_Configured)
Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL);
Endpoint_ClearIN();
+#endif
}
/*******************************************************************************
* sendchar
******************************************************************************/
-#ifdef CONSOLE_ENABLE
-#define SEND_TIMEOUT 5
int8_t sendchar(uint8_t c)
{
- // Not wait once timeouted.
- // Because sendchar() is called so many times, waiting each call causes big lag.
- static bool timeouted = false;
-
- if (USB_DeviceState != DEVICE_STATE_Configured)
- return -1;
-
- uint8_t ep = Endpoint_GetCurrentEndpoint();
- Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM);
- if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) {
- goto ERROR_EXIT;
- }
-
- if (timeouted && !Endpoint_IsReadWriteAllowed()) {
- goto ERROR_EXIT;
- }
-
- timeouted = false;
-
- uint8_t timeout = SEND_TIMEOUT;
- while (!Endpoint_IsReadWriteAllowed()) {
- if (USB_DeviceState != DEVICE_STATE_Configured) {
- goto ERROR_EXIT;
- }
- if (Endpoint_IsStalled()) {
- goto ERROR_EXIT;
- }
- if (!(timeout--)) {
- timeouted = true;
- goto ERROR_EXIT;
- }
- _delay_ms(1);
- }
+ #ifdef TMK_LUFA_DEBUG_SUART
+ xmit(c);
+ #endif
- Endpoint_Write_8(c);
+ #ifdef TMK_LUFA_DEBUG_UART
+ uart_putchar(c);
+ #endif
- // send when bank is full
- if (!Endpoint_IsReadWriteAllowed())
- Endpoint_ClearIN();
+ #ifdef CONSOLE_ENABLE
+ console_putc(c);
+ #endif
- Endpoint_SelectEndpoint(ep);
- return 0;
-ERROR_EXIT:
- Endpoint_SelectEndpoint(ep);
- return -1;
-}
-#else
-int8_t sendchar(uint8_t c)
-{
return 0;
}
-#endif
/*******************************************************************************
USB_Disable();
USB_Init();
-
- // for Console_Task
- USB_Device_EnableSOFEvents();
- print_set_sendchar(sendchar);
}
int main(void) __attribute__ ((weak));
int main(void)
{
setup_mcu();
+
+#ifdef TMK_LUFA_DEBUG_SUART
+ SUART_OUT_DDR |= (1<<SUART_OUT_BIT);
+ SUART_OUT_PORT |= (1<<SUART_OUT_BIT);
+#endif
+
+#ifdef TMK_LUFA_DEBUG_UART
+ uart_init(115200);
+#endif
+
+ // setup sendchar: DO NOT USE print functions before this line
+ print_set_sendchar(sendchar);
+ host_set_driver(&lufa_driver);
+
+ print("\n\nTMK:" STR(TMK_VERSION) "/LUFA\n\n");
+ hook_early_init();
keyboard_setup();
setup_usb();
+#ifdef SLEEP_LED_ENABLE
+ sleep_led_init();
+#endif
+
sei();
- /* wait for USB startup & debug output */
+ keyboard_init();
+
+#ifndef NO_USB_STARTUP_WAIT_LOOP
+ /* wait for USB startup */
while (USB_DeviceState != DEVICE_STATE_Configured) {
#if defined(INTERRUPT_CONTROL_ENDPOINT)
;
#else
USB_USBTask();
#endif
+ hook_usb_startup_wait_loop();
}
- print("USB configured.\n");
-
- /* init modules */
- keyboard_init();
- host_set_driver(&lufa_driver);
-#ifdef SLEEP_LED_ENABLE
- sleep_led_init();
+ print("\nUSB configured.\n");
#endif
- print("Keyboard start.\n");
+ hook_late_init();
+
+ print("\nKeyboard start.\n");
while (1) {
+#ifndef NO_USB_SUSPEND_LOOP
while (USB_DeviceState == DEVICE_STATE_Suspended) {
- print("[s]");
- suspend_power_down();
- if (USB_Device_RemoteWakeupEnabled && suspend_wakeup_condition()) {
- USB_Device_SendRemoteWakeup();
- }
+ hook_usb_suspend_loop();
}
+#endif
keyboard_task();
+#ifdef CONSOLE_ENABLE
+ console_task();
+#endif
+
#if !defined(INTERRUPT_CONTROL_ENDPOINT)
USB_USBTask();
#endif
}
}
+
+
+/* hooks */
+__attribute__((weak))
+void hook_early_init(void) {}
+
+__attribute__((weak))
+void hook_late_init(void) {}
+
+static uint8_t _led_stats = 0;
+ __attribute__((weak))
+void hook_usb_suspend_entry(void)
+{
+ // Turn off LED to save power and keep its status to resotre it later.
+ // LED status will be updated by keyboard_task() in main loop hopefully.
+ _led_stats = keyboard_led_stats;
+ keyboard_led_stats = 0;
+
+ // Calling long task here can prevent USB state transition
+
+ matrix_clear();
+ clear_keyboard();
+#ifdef SLEEP_LED_ENABLE
+ sleep_led_enable();
+#endif
+}
+
+__attribute__((weak))
+void hook_usb_suspend_loop(void)
+{
+#ifndef TMK_LUFA_DEBUG_UART
+ // This corrupts debug print when suspend
+ suspend_power_down();
+#endif
+ if (USB_Device_RemoteWakeupEnabled && suspend_wakeup_condition()) {
+ USB_Device_SendRemoteWakeup();
+ }
+}
+
+__attribute__((weak))
+void hook_usb_wakeup(void)
+{
+ suspend_wakeup_init();
+#ifdef SLEEP_LED_ENABLE
+ sleep_led_disable();
+#endif
+
+ // Restore LED status and update at keyboard_task() in main loop
+ keyboard_led_stats = _led_stats;
+
+ // Calling long task here can prevent USB state transition
+}
+
+__attribute__((weak))
+void hook_usb_startup_wait_loop(void) {}