You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by gn...@apache.org on 2020/09/21 13:40:49 UTC

[incubator-nuttx] branch master updated: input: Q10 BlackBerry Keyboard from Solder Party

This is an automated email from the ASF dual-hosted git repository.

gnutt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 5498f72  input: Q10 BlackBerry Keyboard from Solder Party
5498f72 is described below

commit 5498f72fa509475e06c3c735e8eed38f595a3a10
Author: Brennan Ashton <ba...@brennanashton.com>
AuthorDate: Sun Sep 20 02:37:33 2020 -0700

    input: Q10 BlackBerry Keyboard from Solder Party
    
    This adds support for the Q10 BlackBerry based keyboard
    from Solder Party. https://www.solder.party/docs/keyboard-pmod/
    
    They keyboard device registered at /dev/kbdN is fully compatible
    with hidkbd and has been testing with the Keyboard FeatherWing
    on the nRF52 platform.
    
    The buttons are added as a standard discrete joystick if
    optionally enabled.  The PMOD variant of this does not
    include these buttons, but the Keyboard FeatherWing does.
    This joystick is usually defined at /dev/djoyN and
    can be used with the djoy example application.
    
    Signed-off-by: Brennan Ashton <ba...@brennanashton.com>
---
 drivers/input/Kconfig           |   32 ++
 drivers/input/Make.defs         |    4 +
 drivers/input/spq10kbd.c        | 1074 +++++++++++++++++++++++++++++++++++++++
 include/nuttx/input/djoystick.h |    6 +
 include/nuttx/input/spq10kbd.h  |  124 +++++
 5 files changed, 1240 insertions(+)

diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig
index a23790b..4a898a1 100644
--- a/drivers/input/Kconfig
+++ b/drivers/input/Kconfig
@@ -514,4 +514,36 @@ config NUNCHUCK_NPOLLWAITERS
 
 endif # INPUT_NUNCHUCK
 
+config INPUT_SPQ10KBD
+	bool "Solder Party Q10 BlackBerry Keyboard"
+	default n
+	select I2C
+	---help---
+		Enable the Solder Party Q10 BlackBerry Keyboard support.  This
+		exposes itself as a standard keyboard at /dev/kbdN.
+		This keyboard exists both as a standalone module and integrated
+		into the Solder Party Keyboard FeatherWing.  Information on this
+		can be found at https://www.solder.party/docs/keyboard-pmod/
+
+if INPUT_SPQ10KBD
+
+config SPQ10KBD_DJOY
+    bool "Joystick Interface for Buttons"
+	select DJOYSTICK
+	default n
+
+config SPQ10KBD_REGDBG
+	bool "Keyboard Register Debug"
+	default n
+
+config SPQ10KBD_BUFSIZE
+	int "Keyboard Buffer Size"
+	default 10
+
+config SPQ10KBD_NPOLLWAITERS
+	int "Max Number of Poll Waiters"
+	default 2
+
+endif # INPUT_SPQ10KBD
+
 endif # INPUT
diff --git a/drivers/input/Make.defs b/drivers/input/Make.defs
index f35830f..f8a7054 100644
--- a/drivers/input/Make.defs
+++ b/drivers/input/Make.defs
@@ -99,6 +99,10 @@ ifeq ($(CONFIG_INPUT_NUNCHUCK),y)
   CSRCS += nunchuck.c
 endif
 
+ifeq ($(CONFIG_INPUT_SPQ10KBD),y)
+  CSRCS += spq10kbd.c
+endif
+
 # Include input device driver build support
 
 DEPPATH += --dep-path input
diff --git a/drivers/input/spq10kbd.c b/drivers/input/spq10kbd.c
new file mode 100644
index 0000000..3af8fca
--- /dev/null
+++ b/drivers/input/spq10kbd.c
@@ -0,0 +1,1074 @@
+/****************************************************************************
+ * drivers/input/sp_q10kbd.c
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <debug.h>
+#include <errno.h>
+#include <poll.h>
+#include <fcntl.h>
+
+#include <nuttx/input/spq10kbd.h>
+#include <nuttx/input/djoystick.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <nuttx/fs/fs.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/semaphore.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* This format is used to construct the /dev/kbd[n] device driver path.  It
+ * defined here so that it will be used consistently in all places.
+ */
+
+#define DEV_FORMAT          "/dev/kbd%c"
+#define DEV_NAMELEN         11
+
+/* Number of Joystick discretes */
+
+#define DJOY_NGPIOS  5
+
+/* Bitset of supported Joystick discretes */
+
+#define DJOY_SUPPORTED (DJOY_UP_BIT | DJOY_DOWN_BIT | DJOY_LEFT_BIT | \
+                        DJOY_RIGHT_BIT | DJOY_BUTTON_1_BIT | \
+                        DJOY_BUTTON_2_BIT | DJOY_BUTTON_3_BIT | \
+                        DJOY_BUTTON_4_BIT)
+
+/* Registers */
+
+#define SPQ10KBD_VER  0x01
+#define SPQ10KBD_CFG  0x02
+#define SPQ10KBD_INT  0x03
+#define SPQ10KBD_KEY  0x04
+#define SPQ10KBD_BKL  0x05
+#define SPQ10KBD_DEB  0x06
+#define SPQ10KBD_FRQ  0x07
+#define SPQ10KBD_RST  0x08
+#define SPQ10KBD_FIF  0x09
+
+/* VER */
+
+#define SPQ10KBD_VER_MAJOR_SHIFT    4
+#define SPQ10KBD_VER_MAJOR_MASK     (0xf << SPQ10KBD_VER_MAJOR_SHIFT)
+#define SPQ10KBD_VER_MINOR_SHIFT    0
+#define SPQ10KBD_VER_MINOR_MASK     (0xf << SPQ10KBD_VER_MINOR_SHIFT)
+
+#define SPQ10KBD_VER_00_02          0x0002
+
+/* CFG */
+
+#define SPQ10KBD_CFG_OVERFLOW_ON    (1 << 0)
+#define SPQ10KBD_CFG_OVERFLOW_INT   (1 << 1)
+#define SPQ10KBD_CFG_CAPSLOCK_INT   (1 << 2)
+#define SPQ10KBD_CFG_NUMLOCK_INT    (1 << 3)
+#define SPQ10KBD_CFG_KEY_INT        (1 << 4)
+#define SPQ10KBD_CFG_PANIC_INT      (1 << 5)
+#define SPQ10KBD_CFG_REPORT_MODS    (1 << 6)
+#define SPQ10KBD_CFG_USE_MODS       (1 << 7)
+
+/* INT */
+
+#define SPQ10KBD_INT_OVERFLOW       (1 << 0)
+#define SPQ10KBD_INT_CAPSLOCK       (1 << 1)
+#define SPQ10KBD_INT_NUMLOCK        (1 << 2)
+#define SPQ10KBD_INT_KEY            (1 << 3)
+#define SPQ10KBD_INT_PANIC          (1 << 4)
+
+/* KEY */
+
+#define SPQ10KBD_KEY_COUNT_SHIFT    0
+#define SPQ10KBD_KEY_COUNT_MASK     (0xf << SPQ10KBD_KEY_COUNT_SHIFT)
+#define SPQ10KBD_KEY_CAPSLOCK       (1 << 5)
+#define SPQ10KBD_KEY_NUMLOCK        (1 << 6)
+
+/* FIF */
+
+#define SPQ10KBD_FIF_STATE_SHIFT    0
+#define SPQ10KBD_FIF_STATE_MASK     (0xff << SPQ10KBD_FIF_STATE_SHIFT)
+#define SPQ10KBD_FIF_KEY_SHIFT      8
+#define SPQ10KBD_FIF_KEY_MASK       (0xff << SPQ10KBD_FIF_KEY_SHIFT)
+
+#define KEY_PRESS       0x01
+#define KEY_PRESS_HOLD  0x02
+#define KEY_RELEASE     0x03
+
+/* Special Key Encodings */
+
+#define KEY_BUTTON_FIRST 0x01  /* Start of the button region */
+#define KEY_JOY_UP       0x01
+#define KEY_JOY_DOWN     0x02
+#define KEY_JOY_LEFT     0x03
+#define KEY_JOY_RIGHT    0x04
+#define KEY_JOY_CENTER   0x05
+#define KEY_BTN_LEFT1    0x06
+#define KEY_BTN_RIGHT1   0x07
+#define KEY_BACKSPACE    0x08  /* Normal ASCII */
+#define KEY_TAB          0x09  /* Normal ASCII */
+#define KEY_NL           0x0a  /* Normal ASCII */
+#define KEY_BTN_LEFT2    0x11
+#define KEY_BTN_RIGHT2   0x12
+#define KEY_BUTTON_LAST  0x12  /* End of the button region */
+
+/* Key to joystick mapping */
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+static const djoy_buttonset_t joystick_map[] =
+{
+  0,                  /* No Key */
+  DJOY_UP_BIT,        /* KEY_JOY_UP */
+  DJOY_DOWN_BIT,      /* KEY_JOY_DOWN */
+  DJOY_LEFT_BIT,      /* KEY_JOY_LEFT */
+  DJOY_RIGHT_BIT,     /* KEY_JOY_RIGHT */
+  0,                  /* KEY_JOY_CENTER */
+  DJOY_BUTTON_1_BIT,  /* KEY_BTN_LEFT1 */
+  DJOY_BUTTON_3_BIT,  /* KEY_BTN_RIGHT1 */
+  0,                  /* KEY_BACKSPACE */
+  0,                  /* KEY_TAB */
+  0,                  /* KEY_NL */
+  0,                  /* No Key */
+  0,                  /* No Key */
+  0,                  /* No Key */
+  0,                  /* No Key */
+  0,                  /* No Key */
+  0,                  /* No Key */
+  DJOY_BUTTON_2_BIT,  /* KEY_BTN_LEFT2 */
+  DJOY_BUTTON_4_BIT,  /* KEY_BTN_RIGHT2 */
+};
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct spq10kbd_dev_s
+{
+  FAR const struct spq10kbd_config_s *config;  /* Board configuration data */
+  FAR struct i2c_master_s            *i2c;     /* Saved I2C driver instance */
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+  struct djoy_lowerhalf_s   djoylower;        /* Digital joystick */
+  djoy_interrupt_t          djoyhandle;       /* Joystick handler func */
+  FAR void                 *djoycbarg;        /* Joystick callback arg */
+  djoy_buttonset_t          djoypressmask;    /* Joystick press evts */
+  djoy_buttonset_t          djoyreleasemask;  /* Joystick release evts */
+  djoy_buttonset_t          djoystate;        /* Joystick button state */
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+  sem_t  exclsem;      /* Exclusive access to dev */
+  sem_t  waitsem;      /* Signal waiting thread */
+  bool   waiting;      /* Waiting for keyboard data */
+  struct work_s work;  /* Supports the interrupt handling "bottom half" */
+
+  /* The following is a list if poll structures of threads waiting for
+   * driver events. The 'struct pollfd' reference for each open is also
+   * retained in the f_priv field of the 'struct file'.
+   */
+
+  struct pollfd *fds[CONFIG_SPQ10KBD_NPOLLWAITERS];
+
+  /* Buffer used to collect and buffer incoming keyboard characters */
+
+  uint16_t  headndx;      /* Buffer head index */
+  uint16_t  tailndx;      /* Buffer tail index */
+  uint8_t   kbdbuffer[CONFIG_SPQ10KBD_BUFSIZE];
+
+  uint8_t   crefs;        /* Reference count on the driver instance */
+};
+
+/****************************************************************************
+ * Static Function Prototypes
+ ****************************************************************************/
+
+static int spq10kbd_interrupt(int irq, FAR void *context, FAR void *arg);
+static void spq10kbd_worker(FAR void *arg);
+static void spq10kbd_putreg8(FAR struct spq10kbd_dev_s *priv,
+                             uint8_t regaddr, uint8_t regval);
+static uint8_t spq10kbd_getreg8(FAR struct spq10kbd_dev_s *priv,
+                                uint8_t regaddr);
+static uint16_t spq10kbd_getreg16(FAR struct spq10kbd_dev_s *priv,
+                                  uint8_t regaddr);
+static int spq10kbd_checkver(FAR struct spq10kbd_dev_s *priv);
+static int spq10kbd_reset(FAR struct spq10kbd_dev_s *priv);
+static void spq10kbd_putbuffer(FAR struct spq10kbd_dev_s *priv,
+                               uint8_t keycode);
+
+/* Digital Joystick methods */
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+static djoy_buttonset_t djoy_supported(
+  FAR const struct djoy_lowerhalf_s *lower);
+static djoy_buttonset_t djoy_sample(
+  FAR const struct djoy_lowerhalf_s *lower);
+static void djoy_enable(FAR const struct djoy_lowerhalf_s *lower,
+                        djoy_buttonset_t press, djoy_buttonset_t release,
+                        djoy_interrupt_t handler, FAR void *arg);
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+/* Driver methods. We export the keyboard as a standard character driver */
+
+static int  spq10kbd_open(FAR struct file *filep);
+static int  spq10kbd_close(FAR struct file *filep);
+static ssize_t spq10kbd_read(FAR struct file *filep,
+                             FAR char *buffer, size_t len);
+static ssize_t spq10kbd_write(FAR struct file *filep,
+                              FAR const char *buffer, size_t len);
+static int  spq10kbd_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                          bool setup);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations g_hidkbd_fops =
+{
+  spq10kbd_open,             /* open      */
+  spq10kbd_close,            /* close     */
+  spq10kbd_read,             /* read      */
+  spq10kbd_write,            /* write     */
+  NULL,                      /* seek      */
+  NULL,                      /* ioctl     */
+  spq10kbd_poll              /* poll      */
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+
+/****************************************************************************
+ * Name: djoy_supported
+ *
+ * Description:
+ *   Return the set of buttons supported on the discrete joystick device
+ *
+ ****************************************************************************/
+
+static djoy_buttonset_t djoy_supported(
+  FAR const struct djoy_lowerhalf_s *lower)
+{
+  iinfo("Supported: %02x\n", DJOY_SUPPORTED);
+  return (djoy_buttonset_t)DJOY_SUPPORTED;
+}
+
+/****************************************************************************
+ * Name: djoy_sample
+ *
+ * Description:
+ *   Return the current state of all discrete joystick buttons
+ *
+ ****************************************************************************/
+
+static djoy_buttonset_t djoy_sample(
+  FAR const struct djoy_lowerhalf_s *lower)
+{
+  FAR struct spq10kbd_dev_s  *priv =
+    (FAR struct spq10kbd_dev_s *)(lower->config);
+  return priv->djoystate;
+}
+
+/****************************************************************************
+ * Name: djoy_enable
+ *
+ * Description:
+ *   Enable interrupts on the selected set of joystick buttons.  An empty
+ *   set will disable all interrupts.
+ *
+ ****************************************************************************/
+
+static void djoy_enable(FAR const struct djoy_lowerhalf_s *lower,
+                        djoy_buttonset_t press, djoy_buttonset_t release,
+                        djoy_interrupt_t handler, FAR void *arg)
+{
+  FAR struct spq10kbd_dev_s  *priv =
+    (FAR struct spq10kbd_dev_s *)(lower->config);
+  priv->djoypressmask    = press;
+  priv->djoyreleasemask  = release;
+  priv->djoyhandle       = handler;
+  priv->djoycbarg        = arg;
+}
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+/****************************************************************************
+ * Name: spq10kbd_worker
+ ****************************************************************************/
+
+static void spq10kbd_worker(FAR void *arg)
+{
+  FAR struct spq10kbd_dev_s  *priv = (FAR struct spq10kbd_dev_s *)arg;
+  uint16_t                    regval;
+  uint8_t                     key;
+  uint8_t                     state;
+  int                         ret;
+
+  ret = nxsem_wait_uninterruptible(&priv->exclsem);
+  if (ret < 0)
+    {
+      return;
+    }
+
+  regval = spq10kbd_getreg8(priv, SPQ10KBD_INT);
+  if (regval & SPQ10KBD_INT_KEY)
+    {
+      /* There is a keypress in the FIFO */
+
+      while (spq10kbd_getreg8(priv, SPQ10KBD_KEY) & SPQ10KBD_KEY_COUNT_MASK)
+        {
+          regval = spq10kbd_getreg16(priv, SPQ10KBD_FIF);
+          state = (regval & SPQ10KBD_FIF_STATE_MASK) >> \
+                  SPQ10KBD_FIF_STATE_SHIFT;
+          key = (regval & SPQ10KBD_FIF_KEY_MASK) >> SPQ10KBD_FIF_KEY_SHIFT;
+          if (key <= KEY_BUTTON_LAST &&
+              !(key == KEY_BACKSPACE ||
+                key == KEY_TAB ||
+                key == KEY_NL))
+            {
+#ifdef CONFIG_SPQ10KBD_DJOY
+              if (joystick_map[key] == 0)
+                {
+                  /* Key is not mapped, skip */
+
+                  iinfo("Skipping unmapped key %02x\n", key);
+                }
+
+              switch (state)
+                {
+                  case KEY_PRESS:
+                    iinfo("Button Press: %02x\n", key);
+                    priv->djoystate |= joystick_map[key];
+                    if (priv->djoypressmask & joystick_map[key])
+                      {
+                        priv->djoyhandle(&priv->djoylower,
+                                         priv->djoycbarg);
+                      }
+
+                    break;
+                  case KEY_RELEASE:
+                    iinfo("Button Release: %02x\n", key);
+                    priv->djoystate &= ~joystick_map[key];
+                    if (priv->djoypressmask & joystick_map[key])
+                      {
+                        priv->djoyhandle(&priv->djoylower,
+                                         priv->djoycbarg);
+                      }
+
+                    break;
+                }
+
+              iinfo("Stored state: %02x\n", priv->djoystate);
+#else
+              iinfo("Button Ignored. No joystick interface.\n");
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+            }
+          else if(state == KEY_PRESS)
+            {
+              /* key is a normal ascii character */
+
+              spq10kbd_putbuffer(priv, key);
+
+              /* Notify waiting reads */
+
+              if (priv->waiting == true)
+                {
+                  priv->waiting = false;
+                  nxsem_post(&priv->waitsem);
+                }
+            }
+        }
+    }
+
+  /* Clear interrupt status register */
+
+  spq10kbd_putreg8(priv, SPQ10KBD_INT, 0);
+  nxsem_post(&priv->exclsem);
+}
+
+/****************************************************************************
+ * Name: spq10kbd_interrupt
+ ****************************************************************************/
+
+static int spq10kbd_interrupt(int irq, FAR void *context, FAR void *arg)
+{
+  FAR struct spq10kbd_dev_s  *priv = (FAR struct spq10kbd_dev_s *)arg;
+  int                         ret;
+
+  /* Let the event worker know that it has an interrupt event to handle
+   * It is possbile that we will already have work scheduled from a
+   * previous interrupt event.  That is OK we will service all the events
+   * in the same work job.
+   */
+
+  if (work_available(&priv->work))
+    {
+      ret = work_queue(HPWORK, &priv->work, spq10kbd_worker, priv, 0);
+      if (ret != 0)
+        {
+          ierr("ERROR: Failed to queue work: %d\n", ret);
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_pollnotify
+ ****************************************************************************/
+
+static void spq10kbd_pollnotify(FAR struct spq10kbd_dev_s *priv)
+{
+  int i;
+
+  for (i = 0; i < CONFIG_SPQ10KBD_NPOLLWAITERS; i++)
+    {
+      struct pollfd *fds = priv->fds[i];
+      if (fds)
+        {
+          fds->revents |= (fds->events & POLLIN);
+          if (fds->revents != 0)
+            {
+              uinfo("Report events: %02x\n", fds->revents);
+              nxsem_post(fds->sem);
+            }
+        }
+    }
+}
+
+/****************************************************************************
+ * Name: spq10kbd_open
+ *
+ * Description:
+ *   Standard character driver open method.
+ *
+ ****************************************************************************/
+
+static int spq10kbd_open(FAR struct file *filep)
+{
+  FAR struct inode           *inode;
+  FAR struct spq10kbd_dev_s  *priv;
+
+  DEBUGASSERT(filep && filep->f_inode);
+  inode = filep->f_inode;
+  priv  = inode->i_private;
+
+  /* Increment the reference count on the driver */
+
+  priv->crefs++;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_close
+ *
+ * Description:
+ *   Standard character driver close method.
+ *
+ ****************************************************************************/
+
+static int spq10kbd_close(FAR struct file *filep)
+{
+  FAR struct inode           *inode;
+  FAR struct spq10kbd_dev_s  *priv;
+
+  DEBUGASSERT(filep && filep->f_inode);
+  inode = filep->f_inode;
+  priv  = inode->i_private;
+
+  /* Decrement the reference count on the driver */
+
+  DEBUGASSERT(priv->crefs >= 1);
+
+  priv->crefs--;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_read
+ *
+ * Description:
+ *   Standard character driver read method.
+ *
+ ****************************************************************************/
+
+static ssize_t spq10kbd_read(FAR struct file *filep, FAR char *buffer,
+                             size_t len)
+{
+  FAR struct inode           *inode;
+  FAR struct spq10kbd_dev_s  *priv;
+  size_t                      nbytes;
+  uint16_t                    tail;
+  int                         ret;
+
+  DEBUGASSERT(filep && filep->f_inode && buffer);
+  inode = filep->f_inode;
+  priv  = inode->i_private;
+
+  /* Read data from our internal buffer of received characters */
+
+  ret = nxsem_wait_uninterruptible(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  while (priv->tailndx == priv->headndx)
+    {
+      /* No.. were we open non-blocking? */
+
+      if (filep->f_oflags & O_NONBLOCK)
+        {
+          /* Yes.. then return a failure */
+
+          ret = -EAGAIN;
+          goto errout;
+        }
+      else
+        {
+          priv->waiting = true;
+          nxsem_post(&priv->exclsem);
+          ret = nxsem_wait_uninterruptible(&priv->waitsem);
+          if (ret < 0)
+            {
+              return ret;
+            }
+
+          ret = nxsem_wait_uninterruptible(&priv->exclsem);
+          if (ret < 0)
+            {
+              return ret;
+            }
+        }
+    }
+
+  for (tail = priv->tailndx, nbytes = 0;
+       tail != priv->headndx && nbytes < len;
+       nbytes++)
+    {
+      /* Copy the next keyboard character into the user buffer */
+
+      *buffer++ = priv->kbdbuffer[tail];
+
+      /* Handle wrap-around of the tail index */
+
+      if (++tail >= CONFIG_SPQ10KBD_BUFSIZE)
+        {
+          tail = 0;
+        }
+    }
+
+  ret = nbytes;
+
+  /* Update the tail index (perhaps marking the buffer empty) */
+
+  priv->tailndx = tail;
+
+errout:
+  nxsem_post(&priv->exclsem);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_write
+ *
+ * Description:
+ *   Standard character driver write method.
+ *
+ ****************************************************************************/
+
+static ssize_t spq10kbd_write(FAR struct file *filep, FAR const char *buffer,
+                              size_t len)
+{
+  /* We won't try to write to the keyboard */
+
+  return -ENOSYS;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_poll
+ *
+ * Description:
+ *   Standard character driver poll method.
+ *
+ ****************************************************************************/
+
+static int spq10kbd_poll(FAR struct file *filep, FAR struct pollfd *fds,
+                         bool setup)
+{
+  FAR struct inode           *inode;
+  FAR struct spq10kbd_dev_s  *priv;
+  int                         ret;
+  int                         i;
+
+  DEBUGASSERT(filep && filep->f_inode && fds);
+  inode = filep->f_inode;
+  priv  = inode->i_private;
+
+  /* Make sure that we have exclusive access to the private data structure */
+
+  DEBUGASSERT(priv);
+  ret = nxsem_wait_uninterruptible(&priv->exclsem);
+  if (ret < 0)
+    {
+      return ret;
+    }
+
+  if (setup)
+    {
+      /* This is a request to set up the poll.  Find an available slot for
+       * the poll structure reference
+       */
+
+      for (i = 0; i < CONFIG_SPQ10KBD_NPOLLWAITERS; i++)
+        {
+          /* Find an available slot */
+
+          if (!priv->fds[i])
+            {
+              /* Bind the poll structure and this slot */
+
+              priv->fds[i] = fds;
+              fds->priv    = &priv->fds[i];
+              break;
+            }
+        }
+
+      if (i >= CONFIG_SPQ10KBD_NPOLLWAITERS)
+        {
+          fds->priv    = NULL;
+          ret          = -EBUSY;
+          goto errout;
+        }
+
+      /* Should we immediately notify on any of the requested events? Notify
+       * the POLLIN event if there is buffered keyboard data.
+       */
+
+      if (priv->headndx != priv->tailndx)
+        {
+          spq10kbd_pollnotify(priv);
+        }
+    }
+  else
+    {
+      /* This is a request to tear down the poll. */
+
+      struct pollfd **slot = (struct pollfd **)fds->priv;
+      DEBUGASSERT(slot);
+
+      /* Remove all memory of the poll setup */
+
+      *slot                = NULL;
+      fds->priv            = NULL;
+    }
+
+errout:
+  nxsem_post(&priv->exclsem);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_putbuffer
+ *
+ * Description:
+ *   Add one character to the user buffer.
+ *   Expectation is that we already have exclusive use of the device.
+ *
+ * Input Parameters:
+ *   priv - Driver internal state
+ *   keycode - The value to add to the user buffer
+ *
+ * Returned Value:
+ *   None
+ *
+ ****************************************************************************/
+
+static void spq10kbd_putbuffer(FAR struct spq10kbd_dev_s *priv,
+                               uint8_t keycode)
+{
+  uint16_t head;
+  uint16_t tail;
+
+  DEBUGASSERT(priv);
+
+  /* Copy the next keyboard character into the user buffer. */
+
+  head = priv->headndx;
+  priv->kbdbuffer[head] = keycode;
+
+  /* Increment the head index */
+
+  if (++head >= CONFIG_SPQ10KBD_BUFSIZE)
+    {
+      head = 0;
+    }
+
+  /* If the buffer is full, then increment the tail index to make space.
+   * Drop old unread key presses.
+   */
+
+  tail = priv->tailndx;
+  if (tail == head)
+    {
+      if (++tail >= CONFIG_SPQ10KBD_BUFSIZE)
+        {
+          tail = 0;
+        }
+
+      /* Save the updated tail index */
+
+      priv->tailndx = tail;
+    }
+
+  /* Save the updated head index */
+
+  priv->headndx = head;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_checkver
+ *
+ * Description:
+ *   Read and verify the Q10 Keyboard Controller Version
+ *
+ ****************************************************************************/
+
+static int spq10kbd_checkver(FAR struct spq10kbd_dev_s *priv)
+{
+  uint8_t version;
+
+  /* Read device version  */
+
+  version = spq10kbd_getreg8(priv, SPQ10KBD_VER);
+  iinfo("version: %02x\n", version);
+
+  if (version != SPQ10KBD_VER_00_02)
+    {
+      /* Version is not Correct */
+
+      return -ENODEV;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_reset
+ *
+ * Description:
+ *   Reset the Q10 Keyboard Controller Version
+ *
+ ****************************************************************************/
+
+static int spq10kbd_reset(FAR struct spq10kbd_dev_s *priv)
+{
+  spq10kbd_putreg8(priv, SPQ10KBD_RST, 0xff);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_getreg8
+ *
+ * Description:
+ *   Read from an 8-bit Q10 Keyboard register
+ *
+ ****************************************************************************/
+
+static uint8_t spq10kbd_getreg8(FAR struct spq10kbd_dev_s *priv,
+                                uint8_t regaddr)
+{
+  /* 8-bit data read sequence:
+   *
+   *  Start - I2C_Write_Address - Q10_Reg_Address -
+   *    Repeated_Start - I2C_Read_Address  - Q10_Read_Data - STOP
+   */
+
+  struct i2c_msg_s msg[2];
+  uint8_t          regval;
+  int              ret;
+
+  /* Setup 8-bit Q10 Keyboard address write message */
+
+  msg[0].frequency = priv->config->frequency;  /* I2C frequency */
+  msg[0].addr      = priv->config->address;    /* 7-bit address */
+  msg[0].flags     = 0;                        /* Write transaction, beginning with START */
+  msg[0].buffer    = &regaddr;                 /* Transfer from this address */
+  msg[0].length    = 1;                        /* Send one byte following the address
+                                                * (no STOP) */
+
+  /* Set up the 8-bit Q10 Keyboard data read message */
+
+  msg[1].frequency = priv->config->frequency;  /* I2C frequency */
+  msg[1].addr      = priv->config->address;    /* 7-bit address */
+  msg[1].flags     = I2C_M_READ;               /* Read transaction, beginning with Re-START */
+  msg[1].buffer    = &regval;                  /* Transfer to this address */
+  msg[1].length    = 1;                        /* Receive one byte following the address
+                                                * (then STOP) */
+
+  /* Perform the transfer */
+
+  ret = I2C_TRANSFER(priv->i2c, msg, 2);
+  if (ret < 0)
+    {
+      ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
+      return 0;
+    }
+
+#ifdef CONFIG_SPQ10KBD_REGDBG
+  _err("%02x->%02x\n", regaddr, regval);
+#endif
+  return regval;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_getreg16
+ *
+ * Description:
+ *   Read from an 8-bit Q10 Keyboard register
+ *
+ ****************************************************************************/
+
+static uint16_t spq10kbd_getreg16(FAR struct spq10kbd_dev_s *priv,
+                                  uint8_t regaddr)
+{
+  /* 8-bit data read sequence:
+   *
+   *  Start - I2C_Write_Address - Q10_Reg_Address -
+   *    Repeated_Start - I2C_Read_Address  - Q10_Read_Data - STOP
+   */
+
+  struct i2c_msg_s msg[2];
+  uint8_t          regval[2];
+  uint16_t              ret;
+
+  /* Setup 8-bit Q10 Keyboard address write message */
+
+  msg[0].frequency = priv->config->frequency;  /* I2C frequency */
+  msg[0].addr      = priv->config->address;    /* 7-bit address */
+  msg[0].flags     = 0;                        /* Write transaction, beginning with START */
+  msg[0].buffer    = &regaddr;                 /* Transfer from this address */
+  msg[0].length    = 1;                        /* Send one byte following the address
+                                                * (no STOP) */
+
+  /* Set up the 8-bit Q10 Keyboard data read message */
+
+  msg[1].frequency = priv->config->frequency;  /* I2C frequency */
+  msg[1].addr      = priv->config->address;    /* 7-bit address */
+  msg[1].flags     = I2C_M_READ;               /* Read transaction, beginning with Re-START */
+  msg[1].buffer    = regval;                   /* Transfer to this address */
+  msg[1].length    = 2;                        /* Receive two bytes following the address
+                                                * (then STOP) */
+
+  /* Perform the transfer */
+
+  ret = I2C_TRANSFER(priv->i2c, msg, 2);
+  if (ret < 0)
+    {
+      ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
+      return 0;
+    }
+
+  ret = (regval[1] << 8) | regval[0];
+#ifdef CONFIG_SPQ10KBD_REGDBG
+  _err("%02x->%04x\n", regaddr, ret);
+#endif
+  return ret;
+}
+
+/****************************************************************************
+ * Name: spq10kbd_putreg8
+ *
+ * Description:
+ *   Write a value to an 8-bit Q10 Keyboard register
+ *
+ ****************************************************************************/
+
+static void spq10kbd_putreg8(FAR struct spq10kbd_dev_s *priv,
+                             uint8_t regaddr, uint8_t regval)
+{
+  /* 8-bit data read sequence:
+   *
+   *  Start - I2C_Write_Address - Q10_Reg_Address - Q10_Write_Data - STOP
+   */
+
+  struct i2c_msg_s msg;
+  uint8_t          txbuffer[2];
+  int              ret;
+
+#ifdef CONFIG_SPQ10KBD_REGDBG
+  _err("%02x<-%02x\n", regaddr, regval);
+#endif
+
+  /* Setup to the data to be transferred.  Two bytes:  The Q10 Keyboard
+   * register address followed by one byte of data.
+   */
+
+  txbuffer[0] = regaddr;
+  txbuffer[1] = regval;
+
+  /* Setup 8-bit Q10 Keyboard address write message */
+
+  msg.frequency = priv->config->frequency;  /* I2C frequency */
+  msg.addr      = priv->config->address;    /* 7-bit address */
+  msg.flags     = 0;                        /* Write transaction, beginning with START */
+  msg.buffer    = txbuffer;                 /* Transfer from this address */
+  msg.length    = 2;                        /* Send two byte following the address
+                                             * (then STOP) */
+
+  /* Perform the transfer */
+
+  ret = I2C_TRANSFER(priv->i2c, &msg, 1);
+  if (ret < 0)
+    {
+      ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
+    }
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: spq10kbd_register
+ *
+ * Description:
+ *   Configure the Solder Party Q10 Keyboard to use the provided I2C device
+ *   instance.  This will register the driver as /dev/kbdN where N is the
+ *   minor device number, as well as a joystick at joydevname
+ *
+ * Input Parameters:
+ *   i2c         - An I2C driver instance
+ *   config      - Persistent board configuration data
+ *   kbdminor    - The keyboard input device minor number
+ *   joydevname  - The name of the joystick device /dev/djoyN
+ *
+ * Returned Value:
+ *   Zero is returned on success.  Otherwise, a negated errno value is
+ *   returned to indicate the nature of the failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+int spq10kbd_register(FAR struct i2c_master_s *i2c,
+                      FAR const struct spq10kbd_config_s *config,
+                      char kbdminor, char *joydevname)
+#else
+int spq10kbd_register(FAR struct i2c_master_s *i2c,
+                      FAR const struct spq10kbd_config_s *config,
+                      char kbdminor)
+#endif
+{
+  FAR struct spq10kbd_dev_s *priv;
+  char                       kbddevname[DEV_NAMELEN];
+  int                        ret;
+
+  /* Debug Sanity Checks */
+
+  DEBUGASSERT(i2c != NULL && config != NULL);
+  DEBUGASSERT(config->attach != NULL && config->enable != NULL &&
+              config->clear  != NULL);
+
+  priv = (FAR struct spq10kbd_dev_s *)kmm_zalloc(
+    sizeof(struct spq10kbd_dev_s));
+  if (!priv)
+    {
+      ierr("ERROR: kmm_zalloc(%d) failed\n", sizeof(struct spq10kbd_dev_s));
+      return -ENOMEM;
+    }
+
+  /* Initialize the device driver instance */
+
+  priv->i2c       = i2c;     /* Save the I2C device handle */
+  priv->config    = config;  /* Save the board configuration */
+  priv->tailndx   = 0;       /* Reset keypress buffer state */
+  priv->headndx   = 0;
+  priv->crefs     = 0;       /* Reset referece count to 0 */
+  priv->waiting   = false;
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+  priv->djoylower.config       = (FAR void *)priv;
+  priv->djoylower.dl_supported = djoy_supported;
+  priv->djoylower.dl_sample    = djoy_sample;
+  priv->djoylower.dl_enable    = djoy_enable;
+  priv->djoyhandle             = NULL;
+  priv->djoystate              = 0;
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+  nxsem_init(&priv->exclsem,  0, 1);   /* Initialize device semaphore */
+  nxsem_init(&priv->waitsem, 0, 0);
+
+  /* The waitsem semaphore is used for signaling and, hence, should
+   * not have priority inheritance enabled.
+   */
+
+  nxsem_set_protocol(&priv->waitsem, SEM_PRIO_NONE);
+
+  config->clear(config);
+  config->enable(config, false);
+
+  /* Attach the interrupt handler */
+
+  ret = config->attach(config, spq10kbd_interrupt, priv);
+  if (ret < 0)
+    {
+      ierr("ERROR: Failed to attach interrupt\n");
+      goto errout_with_priv;
+    }
+
+  ret = spq10kbd_checkver(priv);
+
+  if (ret != OK)
+    {
+      /* Did not find a supported device on the bus */
+
+      return ret;
+    }
+
+  spq10kbd_reset(priv);
+
+  /* Start servicing events */
+
+  priv->config->enable(priv->config, true);
+
+  snprintf(kbddevname, DEV_NAMELEN, DEV_FORMAT, kbdminor);
+  iinfo("Registering %s\n", kbddevname);
+  ret = register_driver(kbddevname, &g_hidkbd_fops, 0666, priv);
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+  iinfo("Registering %s\n", joydevname);
+  ret = djoy_register(joydevname, &priv->djoylower);
+#endif  /* CONFIG_SPQ10KBD_DJOY */
+
+  return OK;
+
+errout_with_priv:
+  nxsem_destroy(&priv->exclsem);
+  kmm_free(priv);
+  return ret;
+}
diff --git a/include/nuttx/input/djoystick.h b/include/nuttx/input/djoystick.h
index d59eb5e..49d5f76 100644
--- a/include/nuttx/input/djoystick.h
+++ b/include/nuttx/input/djoystick.h
@@ -235,6 +235,12 @@ struct djoy_lowerhalf_s
   CODE void (*dl_enable)(FAR const struct djoy_lowerhalf_s *lower,
                          djoy_buttonset_t press, djoy_buttonset_t release,
                          djoy_interrupt_t handler, FAR void *arg);
+
+  /* Allow for storing implementation specific data to support cases where
+   * their may be more than one joystick
+   */
+
+  FAR void *config;
 };
 
 /****************************************************************************
diff --git a/include/nuttx/input/spq10kbd.h b/include/nuttx/input/spq10kbd.h
new file mode 100644
index 0000000..4e6aa3d
--- /dev/null
+++ b/include/nuttx/input/spq10kbd.h
@@ -0,0 +1,124 @@
+/****************************************************************************
+ * include/nuttx/input/spq10kbd.h
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/* The q10 keyboard driver exports a standard character driver interface. By
+ * convention, the keyboard driver is exposed as /dev/kbd[n] device driver
+ * path.
+ */
+
+#ifndef __INCLUDE_NUTTX_INPUT_SPQ10KBD_H
+#define __INCLUDE_NUTTX_INPUT_SPQ10KBD_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/i2c/i2c_master.h>
+#include <stdbool.h>
+#include <nuttx/irq.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* A reference to a structure of this type must be passed to the
+ * Solder Party Q10 Keyboard driver.  This structure provides information
+ * about the configuration and provides some board-specific hooks.
+ */
+
+struct spq10kbd_config_s
+{
+  /* Device characterization */
+
+  uint32_t frequency;  /* I2C frequency */
+  uint8_t  address;    /* I2C 7-bit device address */
+
+  /* IRQ/GPIO access callbacks.  These operations all hidden behind
+   * callbacks to isolate the Q10 Keyboard driver from differences in GPIO
+   * interrupt handling by varying boards and MCUs.
+   *
+   * attach  - Attach the Q10 kbd interrupt handler to the GPIO interrupt
+   * enable  - Enable or disable the GPIO interrupt
+   * clear   - Acknowledge/clear any pending GPIO interrupt
+   */
+
+  int  (*attach)(FAR const struct spq10kbd_config_s *state, xcpt_t isr,
+                 FAR void *arg);
+  void (*enable)(FAR const struct spq10kbd_config_s *state, bool enable);
+  void (*clear)(FAR const struct spq10kbd_config_s *state);
+};
+
+/****************************************************************************
+ * Public Data
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: spq10kbd_register
+ *
+ * Description:
+ *   Configure the Solder Party Q10 Keyboard to use the provided I2C device
+ *   instance.  This will register the driver as /dev/kbdN where N is the
+ *   minor device number, as well as a joystick at joydevname
+ *
+ * Input Parameters:
+ *   i2c         - An I2C driver instance
+ *   config      - Persistent board configuration data
+ *   kbdminor    - The keyboard input device minor number
+ *   joydevname  - The name of the joystick device /dev/djoyN
+ *
+ * Returned Value:
+ *   Zero is returned on success.  Otherwise, a negated errno value is
+ *   returned to indicate the nature of the failure.
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_SPQ10KBD_DJOY
+int spq10kbd_register(FAR struct i2c_master_s *i2c,
+                      FAR const struct spq10kbd_config_s *config,
+                      char kbdminor, char *joydevname);
+#else
+int spq10kbd_register(FAR struct i2c_master_s *i2c,
+                      FAR const struct spq10kbd_config_s *config,
+                      char kbdminor);
+#endif
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_INPUT_SPQ10KBD_H */