diff options
Diffstat (limited to 'platforms/chibios/sleep_led.c')
| -rw-r--r-- | platforms/chibios/sleep_led.c | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/platforms/chibios/sleep_led.c b/platforms/chibios/sleep_led.c new file mode 100644 index 000000000..477056a45 --- /dev/null +++ b/platforms/chibios/sleep_led.c | |||
| @@ -0,0 +1,192 @@ | |||
| 1 | #include <ch.h> | ||
| 2 | #include <hal.h> | ||
| 3 | |||
| 4 | #include "led.h" | ||
| 5 | #include "sleep_led.h" | ||
| 6 | |||
| 7 | /* All right, we go the "software" way: timer, toggle LED in interrupt. | ||
| 8 | * Based on hasu's code for AVRs. | ||
| 9 | * Use LP timer on Kinetises, TIM14 on STM32F0. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #ifndef SLEEP_LED_GPT_DRIVER | ||
| 13 | # if defined(STM32F0XX) | ||
| 14 | # define SLEEP_LED_GPT_DRIVER GPTD14 | ||
| 15 | # endif | ||
| 16 | #endif | ||
| 17 | |||
| 18 | #if defined(KL2x) || defined(K20x) || defined(SLEEP_LED_GPT_DRIVER) /* common parts for timers/interrupts */ | ||
| 19 | |||
| 20 | /* Breathing Sleep LED brighness(PWM On period) table | ||
| 21 | * (64[steps] * 4[duration]) / 64[PWM periods/s] = 4 second breath cycle | ||
| 22 | * | ||
| 23 | * http://www.wolframalpha.com/input/?i=%28sin%28+x%2F64*pi%29**8+*+255%2C+x%3D0+to+63 | ||
| 24 | * (0..63).each {|x| p ((sin(x/64.0*PI)**8)*255).to_i } | ||
| 25 | */ | ||
| 26 | static const uint8_t breathing_table[64] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 4, 6, 10, 15, 23, 32, 44, 58, 74, 93, 113, 135, 157, 179, 199, 218, 233, 245, 252, 255, 252, 245, 233, 218, 199, 179, 157, 135, 113, 93, 74, 58, 44, 32, 23, 15, 10, 6, 4, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | ||
| 27 | |||
| 28 | void sleep_led_timer_callback(void) { | ||
| 29 | /* Software PWM | ||
| 30 | * timer:1111 1111 1111 1111 | ||
| 31 | * \_____/\/ \_______/____ count(0-255) | ||
| 32 | * \ \______________ duration of step(4) | ||
| 33 | * \__________________ index of step table(0-63) | ||
| 34 | */ | ||
| 35 | |||
| 36 | // this works for cca 65536 irqs/sec | ||
| 37 | static union { | ||
| 38 | uint16_t row; | ||
| 39 | struct { | ||
| 40 | uint8_t count : 8; | ||
| 41 | uint8_t duration : 2; | ||
| 42 | uint8_t index : 6; | ||
| 43 | } pwm; | ||
| 44 | } timer = {.row = 0}; | ||
| 45 | |||
| 46 | timer.row++; | ||
| 47 | |||
| 48 | // LED on | ||
| 49 | if (timer.pwm.count == 0) { | ||
| 50 | led_set(1 << USB_LED_CAPS_LOCK); | ||
| 51 | } | ||
| 52 | // LED off | ||
| 53 | if (timer.pwm.count == breathing_table[timer.pwm.index]) { | ||
| 54 | led_set(0); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | #endif /* common parts for known platforms */ | ||
| 59 | |||
| 60 | #if defined(KL2x) || defined(K20x) /* platform selection: familiar Kinetis chips */ | ||
| 61 | |||
| 62 | /* Use Low Power Timer (LPTMR) */ | ||
| 63 | # define TIMER_INTERRUPT_VECTOR KINETIS_LPTMR0_IRQ_VECTOR | ||
| 64 | # define RESET_COUNTER LPTMR0->CSR |= LPTMRx_CSR_TCF | ||
| 65 | |||
| 66 | /* LPTMR clock options */ | ||
| 67 | # define LPTMR_CLOCK_MCGIRCLK 0 /* 4MHz clock */ | ||
| 68 | # define LPTMR_CLOCK_LPO 1 /* 1kHz clock */ | ||
| 69 | # define LPTMR_CLOCK_ERCLK32K 2 /* external 32kHz crystal */ | ||
| 70 | # define LPTMR_CLOCK_OSCERCLK 3 /* output from OSC */ | ||
| 71 | |||
| 72 | /* Work around inconsistencies in Freescale naming */ | ||
| 73 | # if !defined(SIM_SCGC5_LPTMR) | ||
| 74 | # define SIM_SCGC5_LPTMR SIM_SCGC5_LPTIMER | ||
| 75 | # endif | ||
| 76 | |||
| 77 | /* interrupt handler */ | ||
| 78 | OSAL_IRQ_HANDLER(TIMER_INTERRUPT_VECTOR) { | ||
| 79 | OSAL_IRQ_PROLOGUE(); | ||
| 80 | |||
| 81 | sleep_led_timer_callback(); | ||
| 82 | |||
| 83 | /* Reset the counter */ | ||
| 84 | RESET_COUNTER; | ||
| 85 | |||
| 86 | OSAL_IRQ_EPILOGUE(); | ||
| 87 | } | ||
| 88 | |||
| 89 | /* Initialise the timer */ | ||
| 90 | void sleep_led_init(void) { | ||
| 91 | /* Make sure the clock to the LPTMR is enabled */ | ||
| 92 | SIM->SCGC5 |= SIM_SCGC5_LPTMR; | ||
| 93 | /* Reset LPTMR settings */ | ||
| 94 | LPTMR0->CSR = 0; | ||
| 95 | /* Set the compare value */ | ||
| 96 | LPTMR0->CMR = 0; // trigger on counter value (i.e. every time) | ||
| 97 | |||
| 98 | /* Set up clock source and prescaler */ | ||
| 99 | /* Software PWM | ||
| 100 | * ______ ______ __ | ||
| 101 | * | ON |___OFF___| ON |___OFF___| .... | ||
| 102 | * |<-------------->|<-------------->|<- .... | ||
| 103 | * PWM period PWM period | ||
| 104 | * | ||
| 105 | * R interrupts/period[resolution] | ||
| 106 | * F periods/second[frequency] | ||
| 107 | * R * F interrupts/second | ||
| 108 | */ | ||
| 109 | |||
| 110 | /* === OPTION 1 === */ | ||
| 111 | # if 0 | ||
| 112 | // 1kHz LPO | ||
| 113 | // No prescaler => 1024 irqs/sec | ||
| 114 | // Note: this is too slow for a smooth breathe | ||
| 115 | LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_LPO)|LPTMRx_PSR_PBYP; | ||
| 116 | # endif /* OPTION 1 */ | ||
| 117 | |||
| 118 | /* === OPTION 2 === */ | ||
| 119 | # if 1 | ||
| 120 | // nMHz IRC (n=4 on KL25Z, KL26Z and K20x; n=2 or 8 on KL27Z) | ||
| 121 | MCG->C2 |= MCG_C2_IRCS; // fast (4MHz) internal ref clock | ||
| 122 | # if defined(KL27) // divide the 8MHz IRC by 2, to have the same MCGIRCLK speed as others | ||
| 123 | MCG->MC |= MCG_MC_LIRC_DIV2_DIV2; | ||
| 124 | # endif /* KL27 */ | ||
| 125 | MCG->C1 |= MCG_C1_IRCLKEN; // enable internal ref clock | ||
| 126 | // to work in stop mode, also MCG_C1_IREFSTEN | ||
| 127 | // Divide 4MHz by 2^N (N=6) => 62500 irqs/sec => | ||
| 128 | // => approx F=61, R=256, duration = 4 | ||
| 129 | LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_MCGIRCLK) | LPTMRx_PSR_PRESCALE(6); | ||
| 130 | # endif /* OPTION 2 */ | ||
| 131 | |||
| 132 | /* === OPTION 3 === */ | ||
| 133 | # if 0 | ||
| 134 | // OSC output (external crystal), usually 8MHz or 16MHz | ||
| 135 | OSC0->CR |= OSC_CR_ERCLKEN; // enable ext ref clock | ||
| 136 | // to work in stop mode, also OSC_CR_EREFSTEN | ||
| 137 | // Divide by 2^N | ||
| 138 | LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_OSCERCLK)|LPTMRx_PSR_PRESCALE(7); | ||
| 139 | # endif /* OPTION 3 */ | ||
| 140 | /* === END OPTIONS === */ | ||
| 141 | |||
| 142 | /* Interrupt on TCF set (compare flag) */ | ||
| 143 | nvicEnableVector(LPTMR0_IRQn, 2); // vector, priority | ||
| 144 | LPTMR0->CSR |= LPTMRx_CSR_TIE; | ||
| 145 | } | ||
| 146 | |||
| 147 | void sleep_led_enable(void) { | ||
| 148 | /* Enable the timer */ | ||
| 149 | LPTMR0->CSR |= LPTMRx_CSR_TEN; | ||
| 150 | } | ||
| 151 | |||
| 152 | void sleep_led_disable(void) { | ||
| 153 | /* Disable the timer */ | ||
| 154 | LPTMR0->CSR &= ~LPTMRx_CSR_TEN; | ||
| 155 | } | ||
| 156 | |||
| 157 | void sleep_led_toggle(void) { | ||
| 158 | /* Toggle the timer */ | ||
| 159 | LPTMR0->CSR ^= LPTMRx_CSR_TEN; | ||
| 160 | } | ||
| 161 | |||
| 162 | #elif defined(SLEEP_LED_GPT_DRIVER) | ||
| 163 | |||
| 164 | static void gptTimerCallback(GPTDriver *gptp) { | ||
| 165 | (void)gptp; | ||
| 166 | sleep_led_timer_callback(); | ||
| 167 | } | ||
| 168 | |||
| 169 | static const GPTConfig gptcfg = {1000000, gptTimerCallback, 0, 0}; | ||
| 170 | |||
| 171 | /* Initialise the timer */ | ||
| 172 | void sleep_led_init(void) { gptStart(&SLEEP_LED_GPT_DRIVER, &gptcfg); } | ||
| 173 | |||
| 174 | void sleep_led_enable(void) { gptStartContinuous(&SLEEP_LED_GPT_DRIVER, gptcfg.frequency / 0xFFFF); } | ||
| 175 | |||
| 176 | void sleep_led_disable(void) { gptStopTimer(&SLEEP_LED_GPT_DRIVER); } | ||
| 177 | |||
| 178 | void sleep_led_toggle(void) { (SLEEP_LED_GPT_DRIVER.state == GPT_READY) ? sleep_led_enable() : sleep_led_disable(); } | ||
| 179 | |||
| 180 | #else /* platform selection: not on familiar chips */ | ||
| 181 | |||
| 182 | void sleep_led_init(void) {} | ||
| 183 | |||
| 184 | void sleep_led_enable(void) { led_set(1 << USB_LED_CAPS_LOCK); } | ||
| 185 | |||
| 186 | void sleep_led_disable(void) { led_set(0); } | ||
| 187 | |||
| 188 | void sleep_led_toggle(void) { | ||
| 189 | // not implemented | ||
| 190 | } | ||
| 191 | |||
| 192 | #endif /* platform selection */ | ||
