summaryrefslogtreecommitdiffstats
path: root/tmk_core/common/avr/suspend.c
blob: c59c19688005c095b3d48db7cf3773a4193dcaab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#include <stdbool.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include "matrix.h"
#include "action.h"
#include "suspend_avr.h"
#include "suspend.h"
#include "timer.h"
#include "led.h"
#include "host.h"
#include "rgblight_reconfig.h"

#ifdef PROTOCOL_LUFA
#    include "lufa.h"
#endif

#ifdef BACKLIGHT_ENABLE
#    include "backlight.h"
#endif

#ifdef AUDIO_ENABLE
#    include "audio.h"
#endif /* AUDIO_ENABLE */

#if defined(RGBLIGHT_SLEEP) && defined(RGBLIGHT_ENABLE)
#    include "rgblight.h"
extern rgblight_config_t rgblight_config;
static bool              rgblight_enabled;
static bool              is_suspended;
#endif

#define wdt_intr_enable(value)                                                                                                                                                         \
    __asm__ __volatile__("in __tmp_reg__,__SREG__"                                                                                                                                     \
                         "\n\t"                                                                                                                                                        \
                         "cli"                                                                                                                                                         \
                         "\n\t"                                                                                                                                                        \
                         "wdr"                                                                                                                                                         \
                         "\n\t"                                                                                                                                                        \
                         "sts %0,%1"                                                                                                                                                   \
                         "\n\t"                                                                                                                                                        \
                         "out __SREG__,__tmp_reg__"                                                                                                                                    \
                         "\n\t"                                                                                                                                                        \
                         "sts %0,%2"                                                                                                                                                   \
                         "\n\t"                                                                                                                                                        \
                         : /* no outputs */                                                                                                                                            \
                         : "M"(_SFR_MEM_ADDR(_WD_CONTROL_REG)), "r"(_BV(_WD_CHANGE_BIT) | _BV(WDE)), "r"((uint8_t)((value & 0x08 ? _WD_PS3_MASK : 0x00) | _BV(WDIE) | (value & 0x07))) \
                         : "r0")

/** \brief Suspend idle
 *
 * FIXME: needs doc
 */
void suspend_idle(uint8_t time) {
    cli();
    set_sleep_mode(SLEEP_MODE_IDLE);
    sleep_enable();
    sei();
    sleep_cpu();
    sleep_disable();
}

// TODO: This needs some cleanup

/** \brief Run keyboard 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(); }

#ifndef NO_SUSPEND_POWER_DOWN
/** \brief Power down MCU with watchdog timer
 *
 * wdto: watchdog timer timeout defined in <avr/wdt.h>
 *          WDTO_15MS
 *          WDTO_30MS
 *          WDTO_60MS
 *          WDTO_120MS
 *          WDTO_250MS
 *          WDTO_500MS
 *          WDTO_1S
 *          WDTO_2S
 *          WDTO_4S
 *          WDTO_8S
 */
static uint8_t wdt_timeout = 0;

/** \brief Power down
 *
 * FIXME: needs doc
 */
static void power_down(uint8_t wdto) {
#    ifdef PROTOCOL_LUFA
    if (USB_DeviceState == DEVICE_STATE_Configured) return;
#    endif
    wdt_timeout = wdto;

    // Watchdog Interrupt Mode
    wdt_intr_enable(wdto);

#    ifdef BACKLIGHT_ENABLE
    backlight_set(0);
#    endif

    // Turn off LED indicators
    uint8_t leds_off = 0;
#    if defined(BACKLIGHT_CAPS_LOCK) && defined(BACKLIGHT_ENABLE)
    if (is_backlight_enabled()) {
        // Don't try to turn off Caps Lock indicator as it is backlight and backlight is already off
        leds_off |= (1 << USB_LED_CAPS_LOCK);
    }
#    endif
    led_set(leds_off);

#    ifdef AUDIO_ENABLE
    // This sometimes disables the start-up noise, so it's been disabled
    // stop_all_notes();
#    endif /* AUDIO_ENABLE */
#    if defined(RGBLIGHT_SLEEP) && defined(RGBLIGHT_ENABLE)
#        ifdef RGBLIGHT_ANIMATIONS
    rgblight_timer_disable();
#        endif
    if (!is_suspended) {
        is_suspended     = true;
        rgblight_enabled = rgblight_config.enable;
        rgblight_disable_noeeprom();
    }
#    endif
    suspend_power_down_kb();

    // TODO: more power saving
    // See PicoPower application note
    // - I/O port input with pullup
    // - prescale clock
    // - BOD disable
    // - Power Reduction Register PRR
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    sei();
    sleep_cpu();
    sleep_disable();

    // Disable watchdog after sleep
    wdt_disable();
}
#endif

/** \brief Suspend power down
 *
 * FIXME: needs doc
 */
void suspend_power_down(void) {
    suspend_power_down_kb();

#ifndef NO_SUSPEND_POWER_DOWN
    power_down(WDTO_15MS);
#endif
}

__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) {
    // clear keyboard state
    clear_keyboard();
#ifdef BACKLIGHT_ENABLE
    backlight_init();
#endif
    led_set(host_keyboard_leds());
#if defined(RGBLIGHT_SLEEP) && defined(RGBLIGHT_ENABLE)
    is_suspended = false;
    if (rgblight_enabled) {
#    ifdef BOOTLOADER_TEENSY
        wait_ms(10);
#    endif
        rgblight_enable_noeeprom();
    }
#    ifdef RGBLIGHT_ANIMATIONS
    rgblight_timer_enable();
#    endif
#endif
    suspend_wakeup_init_kb();
}

#ifndef NO_SUSPEND_POWER_DOWN
/* watchdog timeout */
ISR(WDT_vect) {
    // compensate timer for sleep
    switch (wdt_timeout) {
        case WDTO_15MS:
            timer_count += 15 + 2;  // WDTO_15MS + 2(from observation)
            break;
        default:;
    }
}
#endif