| /**************************************************************************** |
| * drivers/input/spq10kbd.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 <assert.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/mutex.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 */ |
| |
| mutex_t lock; /* 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 */ |
| NULL, /* mmap */ |
| NULL, /* truncate */ |
| 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 = nxmutex_lock(&priv->lock); |
| 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); |
| nxmutex_unlock(&priv->lock); |
| } |
| |
| /**************************************************************************** |
| * 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_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; |
| |
| 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; |
| |
| 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(buffer); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Read data from our internal buffer of received characters */ |
| |
| ret = nxmutex_lock(&priv->lock); |
| 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; |
| nxmutex_unlock(&priv->lock); |
| ret = nxsem_wait_uninterruptible(&priv->waitsem); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| ret = nxmutex_lock(&priv->lock); |
| 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: |
| nxmutex_unlock(&priv->lock); |
| 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(fds); |
| inode = filep->f_inode; |
| priv = inode->i_private; |
| |
| /* Make sure that we have exclusive access to the private data structure */ |
| |
| DEBUGASSERT(priv); |
| ret = nxmutex_lock(&priv->lock); |
| 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) |
| { |
| poll_notify(&fds, 1, POLLIN); |
| } |
| } |
| 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: |
| nxmutex_unlock(&priv->lock); |
| 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 = 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 reference count to 0 */ |
| priv->waiting = false; |
| |
| #ifdef CONFIG_SPQ10KBD_DJOY |
| priv->djoylower.config = 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 */ |
| |
| nxmutex_init(&priv->lock); /* Initialize device mutex */ |
| nxsem_init(&priv->waitsem, 0, 0); |
| |
| 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, sizeof(kbddevname), 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: |
| nxmutex_destroy(&priv->lock); |
| nxsem_destroy(&priv->waitsem); |
| kmm_free(priv); |
| return ret; |
| } |