diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index a23790b6f1c..4a898a1b741 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 f35830fad74..f8a70549753 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 00000000000..3af8fca7fe3 --- /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 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * 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 = ®addr; /* 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 = ®val; /* 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 = ®addr; /* 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 d59eb5e521f..49d5f76d37b 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 00000000000..4e6aa3d96cd --- /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 +#include +#include +#include + +/**************************************************************************** + * 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 */