forked from mfulz_github/qmk_firmware
		
	
		
			
				
	
	
		
			220 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 | 
						|
NeXT non-ADB Keyboard Protocol
 | 
						|
 | 
						|
Copyright 2013, Benjamin Gould (bgould@github.com)
 | 
						|
 | 
						|
Based on:
 | 
						|
TMK firmware code Copyright 2011,2012 Jun WAKO <wakojun@gmail.com>
 | 
						|
Arduino code by "Ladyada" Limor Fried (http://ladyada.net/, http://adafruit.com/), released under BSD license
 | 
						|
 | 
						|
Timing reference thanks to http://m0115.web.fc2.com/ (dead link), http://cfile7.uf.tistory.com/image/14448E464F410BF22380BB
 | 
						|
Pinouts thanks to http://www.68k.org/~degs/nextkeyboard.html
 | 
						|
Keycodes from http://ftp.netbsd.org/pub/NetBSD/NetBSD-release-6/src/sys/arch/next68k/dev/
 | 
						|
 | 
						|
This software is licensed with a Modified BSD License.
 | 
						|
All of this is supposed to be Free Software, Open Source, DFSG-free,
 | 
						|
GPL-compatible, and OK to use in both free and proprietary applications.
 | 
						|
Additions and corrections to this file are welcome.
 | 
						|
 | 
						|
Redistribution and use in source and binary forms, with or without
 | 
						|
modification, are permitted provided that the following conditions are met:
 | 
						|
 | 
						|
* Redistributions of source code must retain the above copyright
 | 
						|
  notice, this list of conditions and the following disclaimer.
 | 
						|
 | 
						|
* Redistributions in binary form must reproduce the above copyright
 | 
						|
  notice, this list of conditions and the following disclaimer in
 | 
						|
  the documentation and/or other materials provided with the
 | 
						|
  distribution.
 | 
						|
 | 
						|
* Neither the name of the copyright holders nor the names of
 | 
						|
  contributors may be used to endorse or promote products derived
 | 
						|
  from this software without specific prior written permission.
 | 
						|
 | 
						|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 | 
						|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 | 
						|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 | 
						|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 | 
						|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 | 
						|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 | 
						|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 | 
						|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 | 
						|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 | 
						|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
						|
POSSIBILITY OF SUCH DAMAGE.
 | 
						|
 | 
						|
*/
 | 
						|
 | 
						|
#include <stdint.h>
 | 
						|
#include <stdbool.h>
 | 
						|
#include <util/atomic.h>
 | 
						|
#include <util/delay.h>
 | 
						|
#include "next_kbd.h"
 | 
						|
#include "debug.h"
 | 
						|
 | 
						|
static inline void     out_lo(void);
 | 
						|
static inline void     out_hi(void);
 | 
						|
static inline void     query(void);
 | 
						|
static inline void     reset(void);
 | 
						|
static inline uint32_t response(void);
 | 
						|
 | 
						|
/* The keyboard sends signal with 50us pulse width on OUT line
 | 
						|
 * while it seems to miss the 50us pulse on In line.
 | 
						|
 * next_kbd_set_leds() often fails to sync LED status with 50us
 | 
						|
 * but it works well with 51us(+1us) on TMK converter(ATMeaga32u2) at least.
 | 
						|
 * TODO: test on Teensy and Pro Micro configuration
 | 
						|
 */
 | 
						|
#define out_hi_delay(intervals)                       \
 | 
						|
    do {                                              \
 | 
						|
        out_hi();                                     \
 | 
						|
        _delay_us((NEXT_KBD_TIMING + 1) * intervals); \
 | 
						|
    } while (0);
 | 
						|
#define out_lo_delay(intervals)                       \
 | 
						|
    do {                                              \
 | 
						|
        out_lo();                                     \
 | 
						|
        _delay_us((NEXT_KBD_TIMING + 1) * intervals); \
 | 
						|
    } while (0);
 | 
						|
#define query_delay(intervals)                        \
 | 
						|
    do {                                              \
 | 
						|
        query();                                      \
 | 
						|
        _delay_us((NEXT_KBD_TIMING + 1) * intervals); \
 | 
						|
    } while (0);
 | 
						|
#define reset_delay(intervals)                        \
 | 
						|
    do {                                              \
 | 
						|
        reset();                                      \
 | 
						|
        _delay_us((NEXT_KBD_TIMING + 1) * intervals); \
 | 
						|
    } while (0);
 | 
						|
 | 
						|
void next_kbd_init(void) {
 | 
						|
    out_hi();
 | 
						|
    NEXT_KBD_IN_DDR &= ~(1 << NEXT_KBD_IN_BIT);  // KBD_IN  to input
 | 
						|
    NEXT_KBD_IN_PORT |= (1 << NEXT_KBD_IN_BIT);  // KBD_IN  pull up
 | 
						|
 | 
						|
    query_delay(5);
 | 
						|
    reset_delay(8);
 | 
						|
 | 
						|
    query_delay(5);
 | 
						|
    reset_delay(8);
 | 
						|
}
 | 
						|
 | 
						|
void next_kbd_set_leds(bool left, bool right) {
 | 
						|
    cli();
 | 
						|
    out_lo_delay(9);
 | 
						|
 | 
						|
    out_hi_delay(3);
 | 
						|
    out_lo_delay(1);
 | 
						|
 | 
						|
    if (left) {
 | 
						|
        out_hi_delay(1);
 | 
						|
    } else {
 | 
						|
        out_lo_delay(1);
 | 
						|
    }
 | 
						|
 | 
						|
    if (right) {
 | 
						|
        out_hi_delay(1);
 | 
						|
    } else {
 | 
						|
        out_lo_delay(1);
 | 
						|
    }
 | 
						|
 | 
						|
    out_lo_delay(7);
 | 
						|
    out_hi();
 | 
						|
    sei();
 | 
						|
}
 | 
						|
 | 
						|
#define NEXT_KBD_READ (NEXT_KBD_IN_PIN & (1 << NEXT_KBD_IN_BIT))
 | 
						|
uint32_t next_kbd_recv(void) {
 | 
						|
    // First check to make sure that the keyboard is actually connected;
 | 
						|
    // if not, just return
 | 
						|
    // TODO: reflect the status of the keyboard in a return code
 | 
						|
    if (!NEXT_KBD_READ) {
 | 
						|
        sei();
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    query();
 | 
						|
    uint32_t resp = response();
 | 
						|
 | 
						|
    return resp;
 | 
						|
}
 | 
						|
 | 
						|
static inline uint32_t response(void) {
 | 
						|
    cli();
 | 
						|
 | 
						|
    // try a 5ms read; this should be called after the query method has
 | 
						|
    // been run so if a key is pressed we should get a response within
 | 
						|
    // 5ms; if not then send a reset and exit
 | 
						|
    uint8_t  i             = 0;
 | 
						|
    uint32_t data          = 0;
 | 
						|
    uint16_t reset_timeout = 50000;
 | 
						|
    while (NEXT_KBD_READ && reset_timeout) {
 | 
						|
        asm("");
 | 
						|
        _delay_us(1);
 | 
						|
        reset_timeout--;
 | 
						|
    }
 | 
						|
    if (!reset_timeout) {
 | 
						|
        reset();
 | 
						|
        sei();
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
    _delay_us(NEXT_KBD_TIMING / 2);
 | 
						|
    for (; i < 22; i++) {
 | 
						|
        if (NEXT_KBD_READ) {
 | 
						|
            data |= ((uint32_t)1 << i);
 | 
						|
            /* Note:
 | 
						|
             * My testing with the ATmega32u4 showed that there might
 | 
						|
             * something wrong with the timing here; by the end of the
 | 
						|
             * second data byte some of the modifiers can get bumped out
 | 
						|
             * to the next bit over if we just cycle through the data
 | 
						|
             * based on the expected interval.  There is a bit (i = 10)
 | 
						|
             * in the middle of the data that is always on followed by
 | 
						|
             * one that is always off - so we'll use that to reset our
 | 
						|
             * timing in case we've gotten ahead of the keyboard;
 | 
						|
             */
 | 
						|
            if (i == 10) {
 | 
						|
                i++;
 | 
						|
                while (NEXT_KBD_READ)
 | 
						|
                    ;
 | 
						|
                _delay_us(NEXT_KBD_TIMING / 2);
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            /* redundant - but I don't want to remove if it might screw
 | 
						|
             * up the timing
 | 
						|
             */
 | 
						|
            data |= ((uint32_t)0 << i);
 | 
						|
        }
 | 
						|
        _delay_us(NEXT_KBD_TIMING);
 | 
						|
    }
 | 
						|
 | 
						|
    sei();
 | 
						|
 | 
						|
    return data;
 | 
						|
}
 | 
						|
 | 
						|
static inline void out_lo(void) {
 | 
						|
    NEXT_KBD_OUT_PORT &= ~(1 << NEXT_KBD_OUT_BIT);
 | 
						|
    NEXT_KBD_OUT_DDR |= (1 << NEXT_KBD_OUT_BIT);
 | 
						|
}
 | 
						|
 | 
						|
static inline void out_hi(void) {
 | 
						|
    /* input with pull up */
 | 
						|
    NEXT_KBD_OUT_DDR &= ~(1 << NEXT_KBD_OUT_BIT);
 | 
						|
    NEXT_KBD_OUT_PORT |= (1 << NEXT_KBD_OUT_BIT);
 | 
						|
}
 | 
						|
 | 
						|
static inline void query(void) {
 | 
						|
    out_lo_delay(5);
 | 
						|
    out_hi_delay(1);
 | 
						|
    out_lo_delay(3);
 | 
						|
    out_hi();
 | 
						|
}
 | 
						|
 | 
						|
static inline void reset(void) {
 | 
						|
    out_lo_delay(1);
 | 
						|
    out_hi_delay(4);
 | 
						|
    out_lo_delay(1);
 | 
						|
    out_hi_delay(6);
 | 
						|
    out_lo_delay(10);
 | 
						|
    out_hi();
 | 
						|
}
 |