9
0
Fork 0
nuttx-bb/nuttx/arch/arm/src/calypso/calypso_keypad.c

360 lines
9.1 KiB
C

/****************************************************************************
* Driver for Calypso keypad hardware
*
* Copyright (C) 2011 Stefan Richter. All rights reserved.
* Author: Stefan Richter <ichgeh@l--putt.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name NuttX nor the names of its 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 <nuttx/config.h>
#include <nuttx/arch.h>
#include <nuttx/irq.h>
#include <nuttx/fs.h>
#include <stdint.h>
#include <semaphore.h>
#include <errno.h>
#include <unistd.h>
#include <sched.h>
#include <arch/calypso/defines.h>
#include <arch/calypso/memory.h>
#include <arch/calypso/timer.h>
/****************************************************************************
* Power control
****************************************************************************/
#include <stdio.h>
#include <nuttx/spi.h>
int poweroff(void)
{
uint16_t tx;
struct spi_dev_s *spi = up_spiinitialize(0);
printf("cutting power...\n\n");
usleep(10000);
SPI_SETBITS(spi, 16);
tx = (1 << 6) | (1 << 1);
SPI_SNDBLOCK(spi, &tx, 1);
tx = (1 << 6) | (30 << 1);
SPI_SNDBLOCK(spi, &tx, 1);
return 0;
}
/****************************************************************************
* HW access
****************************************************************************/
#define BASE_ADDR_ARMIO 0xfffe4800
#define ARMIO_REG(x) ((void *)BASE_ADDR_ARMIO + (x))
enum armio_reg {
LATCH_IN = 0x00,
LATCH_OUT = 0x02,
IO_CNTL = 0x04,
CNTL_REG = 0x06,
LOAD_TIM = 0x08,
KBR_LATCH_REG = 0x0a,
KBC_REG = 0x0c,
BUZZ_LIGHT_REG = 0x0e,
LIGHT_LEVEL = 0x10,
BUZZER_LEVEL = 0x12,
GPIO_EVENT_MODE = 0x14,
KBD_GPIO_INT = 0x16,
KBD_GPIO_MASKIT = 0x18,
GPIO_DEBOUNCING = 0x1a,
GPIO_LATCH = 0x1c,
};
#define KBD_INT (1<<0)
#define GPIO_INT (1<<1)
/****************************************************************************
* Decoder functions for matrix and power button
****************************************************************************/
static int btn_dec(uint32_t *btn_state, uint8_t col, uint8_t reg,
char *buf, size_t buflen, size_t *len)
{
uint8_t diff = (*btn_state ^ reg) & 0x1f;
while(diff) {
uint8_t val = diff & ~(diff-1);
uint8_t sc = val >> 1;
sc |= sc << 2;
sc += col;
sc += (sc&0x20) ? 0x26 : 0x3f;
if(reg & val)
sc |= 0x20;
/* Check for space in buffer and dispatch */
if(*len < buflen)
buf[(*len)++] = sc;
else
break;
/* Only change diff if dispatched/buffer not full */
diff ^= val;
}
/* Store new state of the buttons (but only if they where dispatch) */
*btn_state >>= 5;
#ifdef INCLUDE_ALL_COLS
*btn_state |= (reg ^ diff) << 20;
#else
*btn_state |= (reg ^ diff) << 15;
#endif
return diff;
}
static int pwr_btn_dec(uint32_t *state, uint8_t reg, char *buf, size_t *len)
{
if(reg) {
/*
* Check for pressed power button. If pressed, ignore other
* buttons since it collides with an entire row.
* */
if(~*state & 0x80000000) {
buf[0] = 'z';
*len = 1;
*state |= 0x80000000;
}
return 1; // break loop in caller
} else {
/* Check for released power button. */
if(*state & 0x80000000) {
/* XXX: just cut power, pending better solution */
poweroff();
buf[0] = 'Z';
*len = 1;
*state &= 0x7fffffff;
/*
* Don't scan others when released; might trigger
* false keystrokes otherwise
* */
return 1;
}
}
return 0; // continue with other columns
}
/****************************************************************************
* Keypad: Fileops Prototypes and Structures
****************************************************************************/
typedef FAR struct file file_t;
static int keypad_open(file_t *filep);
static int keypad_close(file_t *filep);
static ssize_t keypad_read(file_t *filep, FAR char *buffer, size_t buflen);
#ifndef CONFIG_DISABLE_POLL
static int keypad_poll(file_t *filep, FAR struct pollfd *fds, bool setup);
#endif
static const struct file_operations keypad_ops = {
keypad_open, /* open */
keypad_close, /* close */
keypad_read, /* read */
0, /* write */
0, /* seek */
0, /* ioctl */
#ifndef CONFIG_DISABLE_POLL
keypad_poll /* poll */
#endif
};
static sem_t kbdsem;
/****************************************************************************
* Keypad: Fileops
****************************************************************************/
static int keypad_open(file_t *filep)
{
register uint16_t reg;
/* Unmask keypad interrupt */
reg = readw(ARMIO_REG(KBD_GPIO_MASKIT));
writew(reg & ~KBD_INT, ARMIO_REG(KBD_GPIO_MASKIT));
return OK;
}
static int keypad_close(file_t *filep)
{
register uint16_t reg;
/* Mask keypad interrupt */
reg = readw(ARMIO_REG(KBD_GPIO_MASKIT));
writew(reg | KBD_INT, ARMIO_REG(KBD_GPIO_MASKIT));
return OK;
}
static ssize_t keypad_read(file_t *filep, FAR char *buf, size_t buflen)
{
static uint32_t btn_state = 0;
register uint16_t reg;
uint16_t col, col_mask;
size_t len = 0;
if(buf == NULL || buflen < 1)
/* Well... nothing to do */
return -EINVAL;
retry:
col = 1;
col_mask = 0x1e;
if(!btn_state) {
/* Drive all cols low such that all buttons cause events */
writew(0, ARMIO_REG(KBC_REG));
/* No button currently pressed, use IRQ */
reg = readw(ARMIO_REG(KBD_GPIO_MASKIT));
writew(reg & ~KBD_INT, ARMIO_REG(KBD_GPIO_MASKIT));
sem_wait(&kbdsem);
} else {
writew(0x1f, ARMIO_REG(KBC_REG));
usleep(80000);
}
/* Scan columns */
#ifdef INCLUDE_ALL_COLS
while(col <= 6) {
#else
while(col <= 5) {
#endif
/*
* Read keypad latch and immediately set new column since
* synchronization takes about 5usec. For the 1st round, the
* interrupt has prepared this and the context switch takes
* long enough to serve as a delay.
* */
reg = readw(ARMIO_REG(KBR_LATCH_REG));
writew(col_mask, ARMIO_REG(KBC_REG));
/* Turn pressed buttons into 1s */
reg = 0x1f & ~reg;
if(col == 1) { // Power/End switch
if(pwr_btn_dec(&btn_state, reg, buf, &len))
break;
} else { // Non-power switches
if(btn_dec(&btn_state, col, reg, buf, buflen, &len))
break;
}
/* Select next column and respective mask */
col_mask = 0x1f & ~(1 << col++);
/*
* We have to wait for synchronization of the inputs. The
* processing is too fast if no/few buttons are processed.
* */
usleep(5);
/*
* XXX: usleep seems to suffer hugh overhead. Better this!?
* If nothing else can be done, it's overhead still wastes
* time 'usefully'.
* */
/*
sched_yield();
up_udelay(2);
*/
}
/* If we don't have anything to return, retry to avoid EOF */
if(!len)
goto retry;
return len;
}
/****************************************************************************
* Keypad interrupt handler
* mask interrupts
* prepare column drivers for scan
* posts keypad semaphore
****************************************************************************/
inline int calypso_kbd_irq(int irq, uint32_t *regs)
{
register uint16_t reg;
/* Mask keypad interrupt */
reg = readw(ARMIO_REG(KBD_GPIO_MASKIT));
writew(reg | KBD_INT, ARMIO_REG(KBD_GPIO_MASKIT));
/* Turn off column drivers */
writew(0x1f, ARMIO_REG(KBC_REG));
/* Let the userspace know */
sem_post(&kbdsem);
return 0;
}
/****************************************************************************
* Initialize device, add /dev/... nodes
****************************************************************************/
void calypso_keypad(void)
{
/* Semaphore; helps leaving IRQ ctx as soon as possible */
sem_init(&kbdsem, 0, 0);
/* Drive cols low in idle state such that all buttons cause events */
writew(0, ARMIO_REG(KBC_REG));
(void)register_driver("/dev/keypad", &keypad_ops, 0444, NULL);
}