]> git.donarmstrong.com Git - qmk_firmware.git/blob - keyboards/infinity60/led_controller.c
Added tmk whitefox led files
[qmk_firmware.git] / keyboards / infinity60 / led_controller.c
1 /*
2 Copyright 2016 flabbergast <s3+flabbergast@sdfeu.org>
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 /*
19  * LED controller code
20  * WF uses IS31FL3731C matrix LED driver from ISSI
21  * datasheet: http://www.issi.com/WW/pdf/31FL3731C.pdf
22  */
23
24 #include "ch.h"
25 #include "hal.h"
26
27 #include "led_controller.h"
28
29 #include "hook.h"
30 #include "suspend.h"
31
32 #include "usb_main.h"
33
34 /* WF LED MAP
35     - digits mean "row" and "col", i.e. 45 means C4-5 in the IS31 datasheet, matrix A
36
37   11 12 13 14 15 16 17 18 21 22 23 24 25 26 27  28
38    31 32 33 34 35 36 37 38 41 42 43 44 45  46   47
39    48 51 52 53 54 55 56 57 58 61 62 63 64   65  66
40     67 68 71 72 73 74 75 76 77 78 81 82  83  84 85
41   86  87  88       91        92  93 (94)  95 96 97
42 */
43
44 /*
45   each page has 0xB4 bytes
46   0 - 0x11: LED control (on/off):
47     order: CA1, CB1, CA2, CB2, .... (CA - matrix A, CB - matrix B)
48       CAn controls Cn-8 .. Cn-1 (LSbit)
49   0x12 - 0x23: blink control (like "LED control")
50   0x24 - 0xB3: PWM control: byte per LED, 0xFF max on
51     order same as above (CA 1st row (8bytes), CB 1st row (8bytes), ...)
52 */
53
54 /* Which LED should be used for CAPS LOCK indicator
55  * The usual Caps Lock position is C4-8, so the address is
56  * 0x24 + (4-1)*0x10 + (8-1) = 0x5B */
57 #if !defined(CAPS_LOCK_LED_ADDRESS)
58 #define CAPS_LOCK_LED_ADDRESS 0x5B
59 #endif
60
61 /* Which LED should breathe during sleep */
62 #if !defined(BREATHE_LED_ADDRESS)
63 #define BREATHE_LED_ADDRESS CAPS_LOCK_LED_ADDRESS
64 #endif
65
66 /* =================
67  * ChibiOS I2C setup
68  * ================= */
69 static const I2CConfig i2ccfg = {
70   400000 // clock speed (Hz); 400kHz max for IS31
71 };
72
73 /* ==============
74  *   variables
75  * ============== */
76 // internal communication buffers
77 uint8_t tx[2] __attribute__((aligned(2)));
78 uint8_t rx[1] __attribute__((aligned(2)));
79
80 // buffer for sending the whole page at once (used also as a temp buffer)
81 uint8_t full_page[0xB4+1] = {0};
82
83 // LED mask (which LEDs are present, selected by bits)
84 const uint8_t is31_wf_leds_mask[0x12] = {
85   0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
86   0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00
87 };
88
89 /* ============================
90  *   communication functions
91  * ============================ */
92 msg_t is31_select_page(uint8_t page) {
93   tx[0] = IS31_COMMANDREGISTER;
94   tx[1] = page;
95   return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT));
96 }
97
98 msg_t is31_write_data(uint8_t page, uint8_t *buffer, uint8_t size) {
99   is31_select_page(page);
100   return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, buffer, size, NULL, 0, US2ST(IS31_TIMEOUT));
101 }
102
103 msg_t is31_write_register(uint8_t page, uint8_t reg, uint8_t data) {
104   is31_select_page(page);
105   tx[0] = reg;
106   tx[1] = data;
107   return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT));
108 }
109
110 msg_t is31_read_register(uint8_t b, uint8_t reg, uint8_t *result) {
111   is31_select_page(b);
112
113   tx[0] = reg;
114   return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 1, result, 1, US2ST(IS31_TIMEOUT));
115 }
116
117 /* ========================
118  * initialise the IS31 chip
119  * ======================== */
120 void is31_init(void) {
121   // just to be sure that it's all zeroes
122   __builtin_memset(full_page,0,0xB4+1);
123   // zero function page, all registers (assuming full_page is all zeroes)
124   is31_write_data(IS31_FUNCTIONREG, full_page, 0xD + 1);
125   // disable hardware shutdown
126   palSetPadMode(GPIOB, 16, PAL_MODE_OUTPUT_PUSHPULL);
127   palSetPad(GPIOB, 16);
128   chThdSleepMilliseconds(10);
129   // software shutdown
130   is31_write_register(IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, 0);
131   chThdSleepMilliseconds(10);
132   // zero function page, all registers
133   is31_write_data(IS31_FUNCTIONREG, full_page, 0xD + 1);
134   chThdSleepMilliseconds(10);
135   // software shutdown disable (i.e. turn stuff on)
136   is31_write_register(IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_ON);
137   chThdSleepMilliseconds(10);
138   // zero all LED registers on all 8 pages
139   uint8_t i;
140   for(i=0; i<8; i++) {
141     is31_write_data(i, full_page, 0xB4 + 1);
142     chThdSleepMilliseconds(1);
143   }
144 }
145
146 /* ==================
147  * LED control thread
148  * ================== */
149 #define LED_MAILBOX_NUM_MSGS 5
150 static msg_t led_mailbox_queue[LED_MAILBOX_NUM_MSGS];
151 mailbox_t led_mailbox;
152 static THD_WORKING_AREA(waLEDthread, 256);
153 static THD_FUNCTION(LEDthread, arg) {
154   (void)arg;
155   chRegSetThreadName("LEDthread");
156
157   uint8_t temp;
158   uint8_t save_page, save_breath1, save_breath2;
159   msg_t msg, retval;
160
161   while(true) {
162     // wait for a message (asynchronous)
163     // (messages are queued (up to LED_MAILBOX_NUM_MSGS) if they can't
164     //  be processed right away)
165     chMBFetch(&led_mailbox, &msg, TIME_INFINITE);
166
167     // process 'msg' here
168     switch(msg) {
169       case LED_MSG_CAPS_ON:
170         // turn caps on on pages 1 and 2
171         is31_write_register(0, CAPS_LOCK_LED_ADDRESS, 0xFF);
172         is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0xFF);
173         is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0xFF);
174         break;
175       case LED_MSG_CAPS_OFF:
176         // turn caps off on pages 1 and 2
177         is31_write_register(0, CAPS_LOCK_LED_ADDRESS, 0);
178         is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0);
179         is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0);
180         break;
181       case LED_MSG_SLEEP_LED_ON:
182         // save current settings
183         is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &save_page);
184         is31_read_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, &save_breath1);
185         is31_read_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, &save_breath2);
186         // use pages 7 and 8 for (hardware) breathing (assuming they're empty)
187         is31_write_register(6, BREATHE_LED_ADDRESS, 0xFF);
188         is31_write_register(7, BREATHE_LED_ADDRESS, 0x00);
189         is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, (6<<4)|6);
190         is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, IS31_REG_BREATHCTRL2_ENABLE|3);
191         retval = MSG_TIMEOUT;
192         temp = 6;
193         while(retval == MSG_TIMEOUT) {
194           // switch to the other page
195           is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, temp);
196           temp = (temp == 6 ? 7 : 6);
197           // the times should be sufficiently long for IS31 to finish switching pages
198           retval = chMBFetch(&led_mailbox, &msg, MS2ST(temp == 6 ? 4000 : 6000));
199         }
200         // received a message (should be a wakeup), so restore previous state
201         chThdSleepMilliseconds(3000); // need to wait until the page change finishes
202         // note: any other messages are queued
203         is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, save_breath1);
204         is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, save_breath2);
205         is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, save_page);
206         break;
207       case LED_MSG_SLEEP_LED_OFF:
208         // should not get here; wakeup should be received in the branch above
209         break;
210       case LED_MSG_ALL_TOGGLE:
211         // read current page into 'temp'
212         is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &temp);
213         chThdSleepMilliseconds(1);
214         // switch to 'the other' page
215         if(temp==2) {
216           is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0);
217         } else {
218           is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 2);
219         }
220         break;
221       case LED_MSG_GAME_TOGGLE:
222         // read current page into 'temp'
223         is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &temp);
224         chThdSleepMilliseconds(1);
225         // switch to 'the other' page
226         if(temp==1) {
227           is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0);
228         } else {
229           is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 1);
230         }
231         break;
232     }
233   }
234 }
235
236 /* LED game mode */
237 const uint8_t led_game[83] = {
238   0x24,
239   0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
240   0x34,
241   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
242   0x44,
243   0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
244   0x54,
245   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
246   0x64,
247   0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
248   0x74,
249   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
250   0x84,
251   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
252   0x94,
253   0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
254   0xA4,
255   0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00,
256 };
257
258 /* ALL LEDs */
259 const uint8_t led_all[83] = {
260   0x24,
261   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
262   0x34,
263   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
264   0x44,
265   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
266   0x54,
267   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
268   0x64,
269   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
270   0x74,
271   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
272   0x84,
273   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
274   0x94,
275   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
276   0xA4,
277   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
278 };
279
280 /* =============
281  * hook into TMK
282  * ============= */
283 void hook_early_init(void) {
284   uint8_t i;
285
286   /* initialise I2C */
287   /* I2C pins */
288   palSetPadMode(GPIOB, 0, PAL_MODE_ALTERNATIVE_2); // PTB0/I2C0/SCL
289   palSetPadMode(GPIOB, 1, PAL_MODE_ALTERNATIVE_2); // PTB1/I2C0/SDA
290   /* start I2C */
291   i2cStart(&I2CD1, &i2ccfg);
292   // try high drive (from kiibohd)
293   I2CD1.i2c->C2 |= I2Cx_C2_HDRS;
294   // try glitch fixing (from kiibohd)
295   I2CD1.i2c->FLT = 4;
296
297   chThdSleepMilliseconds(10);
298
299   /* initialise IS31 chip */
300   is31_init();
301
302   /* enable WF LEDs on all pages */
303   full_page[0] = 0;
304   __builtin_memcpy(full_page+1, is31_wf_leds_mask, 0x12);
305   for(i=0; i<8; i++) {
306     is31_write_data(i, full_page, 1+0x12);
307   }
308
309   /* enable breathing when the displayed page changes */
310   // Fade-in Fade-out, time = 26ms * 2^N, N=3
311   is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, (3<<4)|3);
312   is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, IS31_REG_BREATHCTRL2_ENABLE|3);
313
314   /* Write pages */
315   for(i=0; i<9; i++) {
316     is31_write_data(1,(uint8_t *)(led_game+(9*i)),9);
317     chThdSleepMilliseconds(5);
318     is31_write_data(2,(uint8_t *)(led_all+(9*i)),9);
319     chThdSleepMilliseconds(5);
320   }
321
322   // clean up the capslock LED
323   is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0);
324   is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0);
325
326   /* more time consuming LED processing should be offloaded into
327    * a thread, with asynchronous messaging. */
328   chMBObjectInit(&led_mailbox, led_mailbox_queue, LED_MAILBOX_NUM_MSGS);
329   chThdCreateStatic(waLEDthread, sizeof(waLEDthread), LOWPRIO, LEDthread, NULL);
330 }
331
332 void hook_usb_suspend_entry(void) {
333 #ifdef SLEEP_LED_ENABLE
334   chSysLockFromISR();
335   chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_ON);
336   chSysUnlockFromISR();
337 #endif /* SLEEP_LED_ENABLE */
338 }
339
340 void hook_usb_suspend_loop(void) {
341   chThdSleepMilliseconds(100);
342   /* Remote wakeup */
343   if((USB_DRIVER.status & 2) && suspend_wakeup_condition()) {
344     send_remote_wakeup(&USB_DRIVER);
345   }
346 }
347
348 void hook_usb_wakeup(void) {
349 #ifdef SLEEP_LED_ENABLE
350   chSysLockFromISR();
351   chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_OFF);
352   chSysUnlockFromISR();
353 #endif /* SLEEP_LED_ENABLE */
354 }