2 Copyright 2016 flabbergast <s3+flabbergast@sdfeu.org>
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.
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.
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/>.
20 * WF uses IS31FL3731C matrix LED driver from ISSI
21 * datasheet: http://www.issi.com/WW/pdf/31FL3731C.pdf
27 #include "led_controller.h"
35 - digits mean "row" and "col", i.e. 45 means C4-5 in the IS31 datasheet, matrix A
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
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), ...)
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
61 /* Which LED should breathe during sleep */
62 #if !defined(BREATHE_LED_ADDRESS)
63 #define BREATHE_LED_ADDRESS CAPS_LOCK_LED_ADDRESS
68 * ================= */
69 static const I2CConfig i2ccfg = {
70 400000 // clock speed (Hz); 400kHz max for IS31
76 // internal communication buffers
77 uint8_t tx[2] __attribute__((aligned(2)));
78 uint8_t rx[1] __attribute__((aligned(2)));
80 // buffer for sending the whole page at once (used also as a temp buffer)
81 uint8_t full_page[0xB4+1] = {0};
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
89 /* ============================
90 * communication functions
91 * ============================ */
92 msg_t is31_select_page(uint8_t page) {
93 tx[0] = IS31_COMMANDREGISTER;
95 return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT));
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));
103 msg_t is31_write_register(uint8_t page, uint8_t reg, uint8_t data) {
104 is31_select_page(page);
107 return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT));
110 msg_t is31_read_register(uint8_t b, uint8_t reg, uint8_t *result) {
114 return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 1, result, 1, US2ST(IS31_TIMEOUT));
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);
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
141 is31_write_data(i, full_page, 0xB4 + 1);
142 chThdSleepMilliseconds(1);
146 /* ==================
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) {
155 chRegSetThreadName("LEDthread");
158 uint8_t save_page, save_breath1, save_breath2;
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);
167 // process 'msg' here
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);
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);
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;
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));
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);
207 case LED_MSG_SLEEP_LED_OFF:
208 // should not get here; wakeup should be received in the branch above
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
216 is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0);
218 is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 2);
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
227 is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0);
229 is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 1);
237 const uint8_t led_game[83] = {
239 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
243 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
245 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
247 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
251 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
253 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
255 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00,
259 const uint8_t led_all[83] = {
261 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
263 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
265 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
267 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
269 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
271 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
273 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
275 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
277 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
283 void hook_early_init(void) {
288 palSetPadMode(GPIOB, 0, PAL_MODE_ALTERNATIVE_2); // PTB0/I2C0/SCL
289 palSetPadMode(GPIOB, 1, PAL_MODE_ALTERNATIVE_2); // PTB1/I2C0/SDA
291 i2cStart(&I2CD1, &i2ccfg);
292 // try high drive (from kiibohd)
293 I2CD1.i2c->C2 |= I2Cx_C2_HDRS;
294 // try glitch fixing (from kiibohd)
297 chThdSleepMilliseconds(10);
299 /* initialise IS31 chip */
302 /* enable WF LEDs on all pages */
304 __builtin_memcpy(full_page+1, is31_wf_leds_mask, 0x12);
306 is31_write_data(i, full_page, 1+0x12);
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);
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);
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);
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);
332 void hook_usb_suspend_entry(void) {
333 #ifdef SLEEP_LED_ENABLE
335 chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_ON);
336 chSysUnlockFromISR();
337 #endif /* SLEEP_LED_ENABLE */
340 void hook_usb_suspend_loop(void) {
341 chThdSleepMilliseconds(100);
343 if((USB_DRIVER.status & 2) && suspend_wakeup_condition()) {
344 send_remote_wakeup(&USB_DRIVER);
348 void hook_usb_wakeup(void) {
349 #ifdef SLEEP_LED_ENABLE
351 chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_OFF);
352 chSysUnlockFromISR();
353 #endif /* SLEEP_LED_ENABLE */