diff options
-rw-r--r-- | keyboards/infinity60/led.c | 28 | ||||
-rw-r--r-- | keyboards/infinity60/led_controller.c | 354 | ||||
-rw-r--r-- | keyboards/infinity60/led_controller.h | 93 |
3 files changed, 474 insertions, 1 deletions
diff --git a/keyboards/infinity60/led.c b/keyboards/infinity60/led.c index aed66c7c03..e17a5dd0d3 100644 --- a/keyboards/infinity60/led.c +++ b/keyboards/infinity60/led.c @@ -1,5 +1,5 @@ /* -Copyright 2012 Jun Wako <wakojun@gmail.com> +Copyright 2015 Jun Wako <wakojun@gmail.com> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include "led.h" +#include "led_controller.h" +/* WARNING! This function needs to be callable from + * both regular threads and ISRs, unlocked (during resume-from-sleep). + * In particular, I2C functions (interrupt-driven) should NOT be called from here. + */ void led_set(uint8_t usb_led) { +/* + // PTA5: LED (1:on/0:off) + GPIOA->PDDR |= (1<<1); + PORTA->PCR[5] |= PORTx_PCRn_DSE | PORTx_PCRn_MUX(1); + if (usb_led & (1<<USB_LED_CAPS_LOCK)) { + GPIOA->PSOR |= (1<<5); + } else { + GPIOA->PCOR |= (1<<5); + } + */ + if (usb_led & (1<<USB_LED_CAPS_LOCK)) { + // signal the LED control thread + chSysUnconditionalLock(); + chMBPostI(&led_mailbox, LED_MSG_CAPS_ON); + chSysUnconditionalUnlock(); + } else { + // signal the LED control thread + chSysUnconditionalLock(); + chMBPostI(&led_mailbox, LED_MSG_CAPS_OFF); + chSysUnconditionalUnlock(); + } } diff --git a/keyboards/infinity60/led_controller.c b/keyboards/infinity60/led_controller.c new file mode 100644 index 0000000000..bc501d9f29 --- /dev/null +++ b/keyboards/infinity60/led_controller.c @@ -0,0 +1,354 @@ +/* +Copyright 2016 flabbergast <s3+flabbergast@sdfeu.org> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * LED controller code + * WF uses IS31FL3731C matrix LED driver from ISSI + * datasheet: http://www.issi.com/WW/pdf/31FL3731C.pdf + */ + +#include "ch.h" +#include "hal.h" + +#include "led_controller.h" + +#include "hook.h" +#include "suspend.h" + +#include "usb_main.h" + +/* WF LED MAP + - digits mean "row" and "col", i.e. 45 means C4-5 in the IS31 datasheet, matrix A + + 11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 + 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 + 48 51 52 53 54 55 56 57 58 61 62 63 64 65 66 + 67 68 71 72 73 74 75 76 77 78 81 82 83 84 85 + 86 87 88 91 92 93 (94) 95 96 97 +*/ + +/* + each page has 0xB4 bytes + 0 - 0x11: LED control (on/off): + order: CA1, CB1, CA2, CB2, .... (CA - matrix A, CB - matrix B) + CAn controls Cn-8 .. Cn-1 (LSbit) + 0x12 - 0x23: blink control (like "LED control") + 0x24 - 0xB3: PWM control: byte per LED, 0xFF max on + order same as above (CA 1st row (8bytes), CB 1st row (8bytes), ...) +*/ + +/* Which LED should be used for CAPS LOCK indicator + * The usual Caps Lock position is C4-8, so the address is + * 0x24 + (4-1)*0x10 + (8-1) = 0x5B */ +#if !defined(CAPS_LOCK_LED_ADDRESS) +#define CAPS_LOCK_LED_ADDRESS 0x5B +#endif + +/* Which LED should breathe during sleep */ +#if !defined(BREATHE_LED_ADDRESS) +#define BREATHE_LED_ADDRESS CAPS_LOCK_LED_ADDRESS +#endif + +/* ================= + * ChibiOS I2C setup + * ================= */ +static const I2CConfig i2ccfg = { + 400000 // clock speed (Hz); 400kHz max for IS31 +}; + +/* ============== + * variables + * ============== */ +// internal communication buffers +uint8_t tx[2] __attribute__((aligned(2))); +uint8_t rx[1] __attribute__((aligned(2))); + +// buffer for sending the whole page at once (used also as a temp buffer) +uint8_t full_page[0xB4+1] = {0}; + +// LED mask (which LEDs are present, selected by bits) +const uint8_t is31_wf_leds_mask[0x12] = { + 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x7F, 0x00 +}; + +/* ============================ + * communication functions + * ============================ */ +msg_t is31_select_page(uint8_t page) { + tx[0] = IS31_COMMANDREGISTER; + tx[1] = page; + return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT)); +} + +msg_t is31_write_data(uint8_t page, uint8_t *buffer, uint8_t size) { + is31_select_page(page); + return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, buffer, size, NULL, 0, US2ST(IS31_TIMEOUT)); +} + +msg_t is31_write_register(uint8_t page, uint8_t reg, uint8_t data) { + is31_select_page(page); + tx[0] = reg; + tx[1] = data; + return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 2, NULL, 0, US2ST(IS31_TIMEOUT)); +} + +msg_t is31_read_register(uint8_t b, uint8_t reg, uint8_t *result) { + is31_select_page(b); + + tx[0] = reg; + return i2cMasterTransmitTimeout(&I2CD1, IS31_ADDR_DEFAULT, tx, 1, result, 1, US2ST(IS31_TIMEOUT)); +} + +/* ======================== + * initialise the IS31 chip + * ======================== */ +void is31_init(void) { + // just to be sure that it's all zeroes + __builtin_memset(full_page,0,0xB4+1); + // zero function page, all registers (assuming full_page is all zeroes) + is31_write_data(IS31_FUNCTIONREG, full_page, 0xD + 1); + // disable hardware shutdown + palSetPadMode(GPIOB, 16, PAL_MODE_OUTPUT_PUSHPULL); + palSetPad(GPIOB, 16); + chThdSleepMilliseconds(10); + // software shutdown + is31_write_register(IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, 0); + chThdSleepMilliseconds(10); + // zero function page, all registers + is31_write_data(IS31_FUNCTIONREG, full_page, 0xD + 1); + chThdSleepMilliseconds(10); + // software shutdown disable (i.e. turn stuff on) + is31_write_register(IS31_FUNCTIONREG, IS31_REG_SHUTDOWN, IS31_REG_SHUTDOWN_ON); + chThdSleepMilliseconds(10); + // zero all LED registers on all 8 pages + uint8_t i; + for(i=0; i<8; i++) { + is31_write_data(i, full_page, 0xB4 + 1); + chThdSleepMilliseconds(1); + } +} + +/* ================== + * LED control thread + * ================== */ +#define LED_MAILBOX_NUM_MSGS 5 +static msg_t led_mailbox_queue[LED_MAILBOX_NUM_MSGS]; +mailbox_t led_mailbox; +static THD_WORKING_AREA(waLEDthread, 256); +static THD_FUNCTION(LEDthread, arg) { + (void)arg; + chRegSetThreadName("LEDthread"); + + uint8_t temp; + uint8_t save_page, save_breath1, save_breath2; + msg_t msg, retval; + + while(true) { + // wait for a message (asynchronous) + // (messages are queued (up to LED_MAILBOX_NUM_MSGS) if they can't + // be processed right away) + chMBFetch(&led_mailbox, &msg, TIME_INFINITE); + + // process 'msg' here + switch(msg) { + case LED_MSG_CAPS_ON: + // turn caps on on pages 1 and 2 + is31_write_register(0, CAPS_LOCK_LED_ADDRESS, 0xFF); + is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0xFF); + is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0xFF); + break; + case LED_MSG_CAPS_OFF: + // turn caps off on pages 1 and 2 + is31_write_register(0, CAPS_LOCK_LED_ADDRESS, 0); + is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0); + is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0); + break; + case LED_MSG_SLEEP_LED_ON: + // save current settings + is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &save_page); + is31_read_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, &save_breath1); + is31_read_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, &save_breath2); + // use pages 7 and 8 for (hardware) breathing (assuming they're empty) + is31_write_register(6, BREATHE_LED_ADDRESS, 0xFF); + is31_write_register(7, BREATHE_LED_ADDRESS, 0x00); + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, (6<<4)|6); + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, IS31_REG_BREATHCTRL2_ENABLE|3); + retval = MSG_TIMEOUT; + temp = 6; + while(retval == MSG_TIMEOUT) { + // switch to the other page + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, temp); + temp = (temp == 6 ? 7 : 6); + // the times should be sufficiently long for IS31 to finish switching pages + retval = chMBFetch(&led_mailbox, &msg, MS2ST(temp == 6 ? 4000 : 6000)); + } + // received a message (should be a wakeup), so restore previous state + chThdSleepMilliseconds(3000); // need to wait until the page change finishes + // note: any other messages are queued + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, save_breath1); + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, save_breath2); + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, save_page); + break; + case LED_MSG_SLEEP_LED_OFF: + // should not get here; wakeup should be received in the branch above + break; + case LED_MSG_ALL_TOGGLE: + // read current page into 'temp' + is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &temp); + chThdSleepMilliseconds(1); + // switch to 'the other' page + if(temp==2) { + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0); + } else { + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 2); + } + break; + case LED_MSG_GAME_TOGGLE: + // read current page into 'temp' + is31_read_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, &temp); + chThdSleepMilliseconds(1); + // switch to 'the other' page + if(temp==1) { + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 0); + } else { + is31_write_register(IS31_FUNCTIONREG, IS31_REG_PICTDISP, 1); + } + break; + } + } +} + +/* LED game mode */ +const uint8_t led_game[83] = { + 0x24, + 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x44, + 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x54, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x64, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x74, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x84, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x94, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0xA4, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, +}; + +/* ALL LEDs */ +const uint8_t led_all[83] = { + 0x24, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x34, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x44, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x54, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x64, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x74, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x84, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0x94, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xA4, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +}; + +/* ============= + * hook into TMK + * ============= */ +void hook_early_init(void) { + uint8_t i; + + /* initialise I2C */ + /* I2C pins */ + palSetPadMode(GPIOB, 0, PAL_MODE_ALTERNATIVE_2); // PTB0/I2C0/SCL + palSetPadMode(GPIOB, 1, PAL_MODE_ALTERNATIVE_2); // PTB1/I2C0/SDA + /* start I2C */ + i2cStart(&I2CD1, &i2ccfg); + // try high drive (from kiibohd) + I2CD1.i2c->C2 |= I2Cx_C2_HDRS; + // try glitch fixing (from kiibohd) + I2CD1.i2c->FLT = 4; + + chThdSleepMilliseconds(10); + + /* initialise IS31 chip */ + is31_init(); + + /* enable WF LEDs on all pages */ + full_page[0] = 0; + __builtin_memcpy(full_page+1, is31_wf_leds_mask, 0x12); + for(i=0; i<8; i++) { + is31_write_data(i, full_page, 1+0x12); + } + + /* enable breathing when the displayed page changes */ + // Fade-in Fade-out, time = 26ms * 2^N, N=3 + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL1, (3<<4)|3); + is31_write_register(IS31_FUNCTIONREG, IS31_REG_BREATHCTRL2, IS31_REG_BREATHCTRL2_ENABLE|3); + + /* Write pages */ + for(i=0; i<9; i++) { + is31_write_data(1,(uint8_t *)(led_game+(9*i)),9); + chThdSleepMilliseconds(5); + is31_write_data(2,(uint8_t *)(led_all+(9*i)),9); + chThdSleepMilliseconds(5); + } + + // clean up the capslock LED + is31_write_register(1, CAPS_LOCK_LED_ADDRESS, 0); + is31_write_register(2, CAPS_LOCK_LED_ADDRESS, 0); + + /* more time consuming LED processing should be offloaded into + * a thread, with asynchronous messaging. */ + chMBObjectInit(&led_mailbox, led_mailbox_queue, LED_MAILBOX_NUM_MSGS); + chThdCreateStatic(waLEDthread, sizeof(waLEDthread), LOWPRIO, LEDthread, NULL); +} + +void hook_usb_suspend_entry(void) { +#ifdef SLEEP_LED_ENABLE + chSysLockFromISR(); + chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_ON); + chSysUnlockFromISR(); +#endif /* SLEEP_LED_ENABLE */ +} + +void hook_usb_suspend_loop(void) { + chThdSleepMilliseconds(100); + /* Remote wakeup */ + if((USB_DRIVER.status & 2) && suspend_wakeup_condition()) { + send_remote_wakeup(&USB_DRIVER); + } +} + +void hook_usb_wakeup(void) { +#ifdef SLEEP_LED_ENABLE + chSysLockFromISR(); + chMBPostI(&led_mailbox, LED_MSG_SLEEP_LED_OFF); + chSysUnlockFromISR(); +#endif /* SLEEP_LED_ENABLE */ +} diff --git a/keyboards/infinity60/led_controller.h b/keyboards/infinity60/led_controller.h new file mode 100644 index 0000000000..dcf346e043 --- /dev/null +++ b/keyboards/infinity60/led_controller.h @@ -0,0 +1,93 @@ +/* +Copyright 2016 flabbergast <s3+flabbergast@sdfeu.org> + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _LED_CONTROLLER_H_ +#define _LED_CONTROLLER_H_ + +/* ========================= + * communication functions + * ========================= */ + +msg_t is31_write_data(uint8_t page, uint8_t *buffer, uint8_t size); +msg_t is31_write_register(uint8_t page, uint8_t reg, uint8_t data); +msg_t is31_read_register(uint8_t page, uint8_t reg, uint8_t *result); + +/* ============================= + * IS31 chip related definitions + * ============================= */ + +#define IS31_ADDR_DEFAULT 0x74 + +#define IS31_REG_CONFIG 0x00 +// bits in reg +#define IS31_REG_CONFIG_PICTUREMODE 0x00 +#define IS31_REG_CONFIG_AUTOPLAYMODE 0x08 +#define IS31_REG_CONFIG_AUDIOPLAYMODE 0x18 +// D2:D0 bits are starting frame for autoplay mode + +#define IS31_REG_PICTDISP 0x01 // D2:D0 frame select for picture mode + +#define IS31_REG_AUTOPLAYCTRL1 0x02 +// D6:D4 number of loops (000=infty) +// D2:D0 number of frames to be used + +#define IS31_REG_AUTOPLAYCTRL2 0x03 // D5:D0 delay time (*11ms) + +#define IS31_REG_DISPLAYOPT 0x05 +#define IS31_REG_DISPLAYOPT_INTENSITY_SAME 0x20 // same intensity for all frames +#define IS31_REG_DISPLAYOPT_BLINK_ENABLE 0x8 +// D2:D0 bits blink period time (*0.27s) + +#define IS31_REG_AUDIOSYNC 0x06 +#define IS31_REG_AUDIOSYNC_ENABLE 0x1 + +#define IS31_REG_FRAMESTATE 0x07 + +#define IS31_REG_BREATHCTRL1 0x08 +// D6:D4 fade out time (26ms*2^i) +// D2:D0 fade in time (26ms*2^i) + +#define IS31_REG_BREATHCTRL2 0x09 +#define IS31_REG_BREATHCTRL2_ENABLE 0x10 +// D2:D0 extinguish time (3.5ms*2^i) + +#define IS31_REG_SHUTDOWN 0x0A +#define IS31_REG_SHUTDOWN_ON 0x1 + +#define IS31_REG_AGCCTRL 0x0B +#define IS31_REG_ADCRATE 0x0C + +#define IS31_COMMANDREGISTER 0xFD +#define IS31_FUNCTIONREG 0x0B // helpfully called 'page nine' + +#define IS31_TIMEOUT 10000 // needs to be long enough to write a whole page + +/* ============================== + * LED Thread related definitions + * ============================== */ + +extern mailbox_t led_mailbox; + +// constants for signaling the LED controller thread +#define LED_MSG_CAPS_ON 1 +#define LED_MSG_CAPS_OFF 2 +#define LED_MSG_SLEEP_LED_ON 3 +#define LED_MSG_SLEEP_LED_OFF 4 +#define LED_MSG_ALL_TOGGLE 5 +#define LED_MSG_GAME_TOGGLE 6 + +#endif /* _LED_CONTROLLER_H_ */ |