sleep_led.c
6014 bytes
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 */
26static 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
28void 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 static led_t led_state = {0};
46
47 timer.row++;
48
49 // LED on
50 if (timer.pwm.count == 0) {
51 led_state.caps_lock = true;
52 led_set(led_state.raw);
53 }
54 // LED off
55 if (timer.pwm.count == breathing_table[timer.pwm.index]) {
56 led_state.caps_lock = false;
57 led_set(led_state.raw);
58 }
59}
60
61#endif /* common parts for known platforms */
62
63#if defined(KL2x) || defined(K20x) /* platform selection: familiar Kinetis chips */
64
65/* Use Low Power Timer (LPTMR) */
66# define TIMER_INTERRUPT_VECTOR KINETIS_LPTMR0_IRQ_VECTOR
67# define RESET_COUNTER LPTMR0->CSR |= LPTMRx_CSR_TCF
68
69/* LPTMR clock options */
70# define LPTMR_CLOCK_MCGIRCLK 0 /* 4MHz clock */
71# define LPTMR_CLOCK_LPO 1 /* 1kHz clock */
72# define LPTMR_CLOCK_ERCLK32K 2 /* external 32kHz crystal */
73# define LPTMR_CLOCK_OSCERCLK 3 /* output from OSC */
74
75/* Work around inconsistencies in Freescale naming */
76# if !defined(SIM_SCGC5_LPTMR)
77# define SIM_SCGC5_LPTMR SIM_SCGC5_LPTIMER
78# endif
79
80/* interrupt handler */
81OSAL_IRQ_HANDLER(TIMER_INTERRUPT_VECTOR) {
82 OSAL_IRQ_PROLOGUE();
83
84 sleep_led_timer_callback();
85
86 /* Reset the counter */
87 RESET_COUNTER;
88
89 OSAL_IRQ_EPILOGUE();
90}
91
92/* Initialise the timer */
93void sleep_led_init(void) {
94 /* Make sure the clock to the LPTMR is enabled */
95 SIM->SCGC5 |= SIM_SCGC5_LPTMR;
96 /* Reset LPTMR settings */
97 LPTMR0->CSR = 0;
98 /* Set the compare value */
99 LPTMR0->CMR = 0; // trigger on counter value (i.e. every time)
100
101/* Set up clock source and prescaler */
102/* Software PWM
103 * ______ ______ __
104 * | ON |___OFF___| ON |___OFF___| ....
105 * |<-------------->|<-------------->|<- ....
106 * PWM period PWM period
107 *
108 * R interrupts/period[resolution]
109 * F periods/second[frequency]
110 * R * F interrupts/second
111 */
112
113/* === OPTION 1 === */
114# if 0
115 // 1kHz LPO
116 // No prescaler => 1024 irqs/sec
117 // Note: this is too slow for a smooth breathe
118 LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_LPO)|LPTMRx_PSR_PBYP;
119# endif /* OPTION 1 */
120
121/* === OPTION 2 === */
122# if 1
123 // nMHz IRC (n=4 on KL25Z, KL26Z and K20x; n=2 or 8 on KL27Z)
124 MCG->C2 |= MCG_C2_IRCS; // fast (4MHz) internal ref clock
125# if defined(KL27) // divide the 8MHz IRC by 2, to have the same MCGIRCLK speed as others
126 MCG->MC |= MCG_MC_LIRC_DIV2_DIV2;
127# endif /* KL27 */
128 MCG->C1 |= MCG_C1_IRCLKEN; // enable internal ref clock
129 // to work in stop mode, also MCG_C1_IREFSTEN
130 // Divide 4MHz by 2^N (N=6) => 62500 irqs/sec =>
131 // => approx F=61, R=256, duration = 4
132 LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_MCGIRCLK) | LPTMRx_PSR_PRESCALE(6);
133# endif /* OPTION 2 */
134
135/* === OPTION 3 === */
136# if 0
137 // OSC output (external crystal), usually 8MHz or 16MHz
138 OSC0->CR |= OSC_CR_ERCLKEN; // enable ext ref clock
139 // to work in stop mode, also OSC_CR_EREFSTEN
140 // Divide by 2^N
141 LPTMR0->PSR = LPTMRx_PSR_PCS(LPTMR_CLOCK_OSCERCLK)|LPTMRx_PSR_PRESCALE(7);
142# endif /* OPTION 3 */
143 /* === END OPTIONS === */
144
145 /* Interrupt on TCF set (compare flag) */
146 nvicEnableVector(LPTMR0_IRQn, 2); // vector, priority
147 LPTMR0->CSR |= LPTMRx_CSR_TIE;
148}
149
150void sleep_led_enable(void) {
151 /* Enable the timer */
152 LPTMR0->CSR |= LPTMRx_CSR_TEN;
153}
154
155void sleep_led_disable(void) {
156 /* Disable the timer */
157 LPTMR0->CSR &= ~LPTMRx_CSR_TEN;
158}
159
160void sleep_led_toggle(void) {
161 /* Toggle the timer */
162 LPTMR0->CSR ^= LPTMRx_CSR_TEN;
163}
164
165#elif defined(SLEEP_LED_GPT_DRIVER)
166
167static void gptTimerCallback(GPTDriver *gptp) {
168 (void)gptp;
169 sleep_led_timer_callback();
170}
171
172static const GPTConfig gptcfg = {1000000, gptTimerCallback, 0, 0};
173
174/* Initialise the timer */
175void sleep_led_init(void) {
176 gptStart(&SLEEP_LED_GPT_DRIVER, &gptcfg);
177}
178
179void sleep_led_enable(void) {
180 gptStartContinuous(&SLEEP_LED_GPT_DRIVER, gptcfg.frequency / 0xFFFF);
181}
182
183void sleep_led_disable(void) {
184 gptStopTimer(&SLEEP_LED_GPT_DRIVER);
185}
186
187void sleep_led_toggle(void) {
188 (SLEEP_LED_GPT_DRIVER.state == GPT_READY) ? sleep_led_enable() : sleep_led_disable();
189}
190
191#else /* platform selection: not on familiar chips */
192
193void sleep_led_init(void) {}
194
195void sleep_led_enable(void) {
196 led_set(2); // Caps Lock
197}
198
199void sleep_led_disable(void) {
200 led_set(0);
201}
202
203void sleep_led_toggle(void) {
204 // not implemented
205}
206
207#endif /* platform selection */