From cec203ea80c8e9365bb5f43418fba5971dd4091f Mon Sep 17 00:00:00 2001
From: patrickmt <40182064+patrickmt@users.noreply.github.com>
Date: Fri, 2 Nov 2018 15:30:51 -0400
Subject: [PATCH] USB Suspend for arm_atsam protocol

Rewrote USB state tracking for implementation of suspend state.
Updated suspend.c in entirety.
Main subtasks (generally hardware related) are now run prior to keyboard task.
---
 tmk_core/common/arm_atsam/suspend.c          | 94 +++++++++++++++++---
 tmk_core/protocol/arm_atsam/i2c_master.c     |  5 +-
 tmk_core/protocol/arm_atsam/main_arm_atsam.c | 84 ++++++++++-------
 tmk_core/protocol/arm_atsam/usb/ui.c         |  8 +-
 tmk_core/protocol/arm_atsam/usb/ui.h         |  6 --
 5 files changed, 140 insertions(+), 57 deletions(-)

diff --git a/tmk_core/common/arm_atsam/suspend.c b/tmk_core/common/arm_atsam/suspend.c
index 01d1930ea5..e34965df64 100644
--- a/tmk_core/common/arm_atsam/suspend.c
+++ b/tmk_core/common/arm_atsam/suspend.c
@@ -1,17 +1,85 @@
-/* Copyright 2017 Fred Sundvik
+#include "matrix.h"
+#include "i2c_master.h"
+#include "led_matrix.h"
+#include "suspend.h"
+
+/** \brief Suspend idle
  *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * FIXME: needs doc
  */
+void suspend_idle(uint8_t time) {
+    /* Note: Not used anywhere currently */
+}
 
+/** \brief Run user level Power down
+ *
+ * FIXME: needs doc
+ */
+__attribute__ ((weak))
+void suspend_power_down_user (void) {
+
+}
+
+/** \brief Run keyboard level Power down
+ *
+ * FIXME: needs doc
+ */
+__attribute__ ((weak))
+void suspend_power_down_kb(void) {
+    suspend_power_down_user();
+}
+
+/** \brief Suspend power down
+ *
+ * FIXME: needs doc
+ */
+void suspend_power_down(void)
+{
+    I2C3733_Control_Set(0); //Disable LED driver
+
+    suspend_power_down_kb();
+}
+
+__attribute__ ((weak)) void matrix_power_up(void) {}
+__attribute__ ((weak)) void matrix_power_down(void) {}
+bool suspend_wakeup_condition(void) {
+    matrix_power_up();
+    matrix_scan();
+    matrix_power_down();
+    for (uint8_t r = 0; r < MATRIX_ROWS; r++) {
+        if (matrix_get_row(r)) return true;
+    }
+    return false;
+}
+
+/** \brief run user level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__ ((weak))
+void suspend_wakeup_init_user(void) {
+
+}
+
+/** \brief run keyboard level code immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+__attribute__ ((weak))
+void suspend_wakeup_init_kb(void) {
+    suspend_wakeup_init_user();
+}
+
+/** \brief run immediately after wakeup
+ *
+ * FIXME: needs doc
+ */
+void suspend_wakeup_init(void) {
+    /* If LEDs are set to enabled, enable the hardware */
+    if (led_enabled) {
+        I2C3733_Control_Set(1);
+    }
+
+    suspend_wakeup_init_kb();
+}
 
diff --git a/tmk_core/protocol/arm_atsam/i2c_master.c b/tmk_core/protocol/arm_atsam/i2c_master.c
index 4f5a79e89f..ece9ee5db8 100644
--- a/tmk_core/protocol/arm_atsam/i2c_master.c
+++ b/tmk_core/protocol/arm_atsam/i2c_master.c
@@ -261,8 +261,9 @@ uint8_t I2C3733_Init_Control(void)
 {
     DBGC(DC_I2C3733_INIT_CONTROL_BEGIN);
 
-    srdata.bit.SDB_N = 1;
-    SPI_WriteSRData();
+    //Hardware state shutdown on boot
+    //USB state machine will enable driver when communication is ready
+    I2C3733_Control_Set(0);
 
     CLK_delay_ms(1);
 
diff --git a/tmk_core/protocol/arm_atsam/main_arm_atsam.c b/tmk_core/protocol/arm_atsam/main_arm_atsam.c
index d3dc272ee0..13034a05d1 100644
--- a/tmk_core/protocol/arm_atsam/main_arm_atsam.c
+++ b/tmk_core/protocol/arm_atsam/main_arm_atsam.c
@@ -31,6 +31,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 //From keyboard's directory
 #include "config_led.h"
 
+uint8_t g_usb_state = USB_FSMSTATUS_FSMSTATE_OFF_Val;   //Saved USB state from hardware value to detect changes
+
 void main_subtasks(void);
 uint8_t keyboard_leds(void);
 void send_keyboard(report_keyboard_t *report);
@@ -62,12 +64,6 @@ void send_keyboard(report_keyboard_t *report)
 {
     uint32_t irqflags;
 
-    if (usb_state == USB_STATE_POWERDOWN)
-    {
-        udc_remotewakeup();
-        return;
-    }
-
 #ifdef NKRO_ENABLE
     if (!keymap_config.nkro)
     {
@@ -161,41 +157,56 @@ void send_consumer(uint16_t data)
 #endif //EXTRAKEY_ENABLE
 }
 
-uint8_t g_drvid;
-uint8_t g_usb_sleeping = 0;
-
 void main_subtask_usb_state(void)
 {
-    if (usb_state == USB_STATE_POWERDOWN)
+    static uint32_t fsmstate_on_delay = 0;                          //Delay timer to be sure USB is actually operating before bringing up hardware
+    uint8_t fsmstate_now = USB->DEVICE.FSMSTATUS.reg;               //Current state from hardware register
+
+    if (fsmstate_now == USB_FSMSTATUS_FSMSTATE_SUSPEND_Val)         //If USB SUSPENDED
     {
-        if (!g_usb_sleeping)
+        fsmstate_on_delay = 0;                                      //Clear ON delay timer
+
+        if (g_usb_state != USB_FSMSTATUS_FSMSTATE_SUSPEND_Val)      //If previously not SUSPENDED
         {
-            g_usb_sleeping = 1;
-            if (led_enabled)
+            suspend_power_down();                                   //Run suspend routine
+            g_usb_state = fsmstate_now;                             //Save current USB state
+        }
+    }
+    else if (fsmstate_now == USB_FSMSTATUS_FSMSTATE_SLEEP_Val)      //Else if USB SLEEPING
+    {
+        fsmstate_on_delay = 0;                                      //Clear ON delay timer
+
+        if (g_usb_state != USB_FSMSTATUS_FSMSTATE_SLEEP_Val)        //If previously not SLEEPING
+        {
+            suspend_power_down();                                   //Run suspend routine
+            g_usb_state = fsmstate_now;                             //Save current USB state
+        }
+    }
+    else if (fsmstate_now == USB_FSMSTATUS_FSMSTATE_ON_Val)         //Else if USB ON
+    {
+        if (g_usb_state != USB_FSMSTATUS_FSMSTATE_ON_Val)           //If previously not ON
+        {
+            if (fsmstate_on_delay == 0)                             //If ON delay timer is cleared
             {
-                for (g_drvid = 0; g_drvid < ISSI3733_DRIVER_COUNT; g_drvid++)
-                {
-                    I2C3733_Control_Set(0);
-                }
+                fsmstate_on_delay = CLK_get_ms() + 250;             //Set ON delay timer
+            }
+            else if (CLK_get_ms() > fsmstate_on_delay)              //Else if ON delay timer is active and timed out
+            {
+                suspend_wakeup_init();                              //Run wakeup routine
+                g_usb_state = fsmstate_now;                         //Save current USB state
             }
         }
     }
-    else if (g_usb_sleeping)
+    else                                                            //Else if USB is in a state not being tracked
     {
-        g_usb_sleeping = 0;
-        if (led_enabled)
-        {
-            for (g_drvid = 0; g_drvid < ISSI3733_DRIVER_COUNT; g_drvid++)
-            {
-                I2C3733_Control_Set(1);
-            }
-        }
+        fsmstate_on_delay = 0;                                      //Clear ON delay timer
     }
 }
 
 void main_subtask_led(void)
 {
-    if (g_usb_sleeping) return;
+    if (g_usb_state != USB_FSMSTATUS_FSMSTATE_ON_Val) return; //Only run LED tasks if USB is operating
+
     led_matrix_task();
 }
 
@@ -275,8 +286,8 @@ int main(void)
 
     i2c_led_q_init();
 
-    for (g_drvid = 0; g_drvid < ISSI3733_DRIVER_COUNT; g_drvid++)
-        I2C_LED_Q_ONOFF(g_drvid); //Queue data
+    for (uint8_t drvid = 0; drvid < ISSI3733_DRIVER_COUNT; drvid++)
+        I2C_LED_Q_ONOFF(drvid); //Queue data
 
     keyboard_setup();
 
@@ -294,10 +305,21 @@ int main(void)
 
     while (1)
     {
-        keyboard_task();
-
         main_subtasks(); //Note these tasks will also be run while waiting for USB keyboard polling intervals
 
+        if (g_usb_state == USB_FSMSTATUS_FSMSTATE_SUSPEND_Val || g_usb_state == USB_FSMSTATUS_FSMSTATE_SLEEP_Val)
+        {
+            if (suspend_wakeup_condition())
+            {
+                udc_remotewakeup(); //Send remote wakeup signal
+                wait_ms(50);
+            }
+
+            continue;
+        }
+
+        keyboard_task();
+
 #ifdef CONSOLE_ENABLE
         if (CLK_get_ms() > next_print)
         {
diff --git a/tmk_core/protocol/arm_atsam/usb/ui.c b/tmk_core/protocol/arm_atsam/usb/ui.c
index 031678b643..70a6191098 100644
--- a/tmk_core/protocol/arm_atsam/usb/ui.c
+++ b/tmk_core/protocol/arm_atsam/usb/ui.c
@@ -52,8 +52,6 @@
 #include "samd51j18a.h"
 #include "ui.h"
 
-volatile uint8_t usb_state;
-
 //! Sequence process running each \c SEQUENCE_PERIOD ms
 #define SEQUENCE_PERIOD 150
 
@@ -72,12 +70,12 @@ static void ui_wakeup_handler(void)
 
 void ui_init(void)
 {
-    usb_state = USB_STATE_POWERUP;
+
 }
 
 void ui_powerdown(void)
 {
-    usb_state = USB_STATE_POWERDOWN;
+
 }
 
 void ui_wakeup_enable(void)
@@ -92,7 +90,7 @@ void ui_wakeup_disable(void)
 
 void ui_wakeup(void)
 {
-    usb_state = USB_STATE_POWERUP;
+
 }
 
 void ui_process(uint16_t framenumber)
diff --git a/tmk_core/protocol/arm_atsam/usb/ui.h b/tmk_core/protocol/arm_atsam/usb/ui.h
index 3d899e6694..d1c767d457 100644
--- a/tmk_core/protocol/arm_atsam/usb/ui.h
+++ b/tmk_core/protocol/arm_atsam/usb/ui.h
@@ -47,12 +47,6 @@
 #ifndef _UI_H_
 #define _UI_H_
 
-extern volatile uint8_t usb_state;
-
-#define USB_STATE_UNKNOWN 0
-#define USB_STATE_POWERDOWN 1
-#define USB_STATE_POWERUP 2
-
 //! \brief Initializes the user interface
 void ui_init(void);