/**************************************************************************** * drivers/input/ft5x06.c * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Some of this driver was developed with input from NXP sample code for * the LPCXpresso-LPC54628 board. That sample code as a compatible BSD * license: * * Copyright (c) 2016, Freescale Semiconductor, Inc. * Copyright 2016-2017 NXP * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /* References: * "FT5x06", FocalTech Systems Co., Ltd, D-FT5x06-1212-V4.0, Revised * Dec. 18, 2012 */ /* The FT5x06 Series ICs are single-chip capacitive touch panel controller * ICs with a built-in 8 bit Micro-controller unit (MCU). They adopt the * mutual capacitance approach, which supports true multi-touch capability. * In conjunction with a mutual capacitive touch panel, the FT5x06 have * user-friendly input functions, which can be applied on many portable * devices, such as cellular phones, MIDs, netbook and notebook personal * computers. */ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ft5x06.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Driver support ***********************************************************/ /* This format is used to construct the /dev/input[n] device driver path. It * defined here so that it will be used consistently in all places. */ #define DEV_FORMAT "/dev/input%d" #define DEV_NAMELEN 16 /* In polled mode, the polling rate will decrease when there is no touch * activity. These definitions represent the maximum and the minimum * polling rates. */ #define POLL_MINDELAY MSEC2TICK(50) #define POLL_MAXDELAY MSEC2TICK(200) #define POLL_INCREMENT MSEC2TICK(10) /**************************************************************************** * Private Types ****************************************************************************/ /* This structure describes the state of one FT5x06 driver instance */ struct ft5x06_dev_s { uint8_t crefs; /* Number of times the device * has been opened */ uint8_t nwaiters; /* Number of threads waiting for * FT5x06 data */ volatile bool valid; /* True: New, valid touch data * in touchbuf[] */ #ifdef CONFIG_FT5X06_SINGLEPOINT uint8_t lastid; /* Last reported touch id */ uint8_t lastevent; /* Last reported event */ int16_t lastx; /* Last reported X position */ int16_t lasty; /* Last reported Y position */ #endif sem_t devsem; /* Manages exclusive access to * this structure */ sem_t waitsem; /* Used to wait for the * availability of data */ uint32_t frequency; /* Current I2C frequency */ #ifdef CONFIG_FT5X06_POLLMODE uint32_t delay; /* Current poll delay */ #endif FAR const struct ft5x06_config_s *config; /* Board configuration data */ FAR struct i2c_master_s *i2c; /* Saved I2C driver instance */ struct work_s work; /* Supports the interrupt * handling "bottom half" */ #ifdef CONFIG_FT5X06_POLLMODE struct wdog_s polltimer; /* Poll timer */ #endif uint8_t touchbuf[FT5X06_TOUCH_DATA_LEN]; /* Raw touch data */ /* 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_FT5X06_NPOLLWAITERS]; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static void ft5x06_notify(FAR struct ft5x06_dev_s *priv); static void ft5x06_data_worker(FAR void *arg); #ifdef CONFIG_FT5X06_POLLMODE static void ft5x06_poll_timeout(wdparm_t arg); #else static int ft5x06_data_interrupt(int irq, FAR void *context, FAR void *arg); #endif static ssize_t ft5x06_sample(FAR struct ft5x06_dev_s *priv, FAR char *buffer, size_t len); static ssize_t ft5x06_waitsample(FAR struct ft5x06_dev_s *priv, FAR char *buffer, size_t len); static int ft5x06_bringup(FAR struct ft5x06_dev_s *priv); static void ft5x06_shutdown(FAR struct ft5x06_dev_s *priv); /* Character driver methods */ static int ft5x06_open(FAR struct file *filep); static int ft5x06_close(FAR struct file *filep); static ssize_t ft5x06_read(FAR struct file *filep, FAR char *buffer, size_t len); static int ft5x06_ioctl(FAR struct file *filep, int cmd, unsigned long arg); static int ft5x06_poll(FAR struct file *filep, struct pollfd *fds, bool setup); /**************************************************************************** * Private Data ****************************************************************************/ /* This the vtable that supports the character driver interface */ static const struct file_operations ft5x06_fops = { ft5x06_open, /* open */ ft5x06_close, /* close */ ft5x06_read, /* read */ NULL, /* write */ NULL, /* seek */ ft5x06_ioctl, /* ioctl */ ft5x06_poll /* poll */ #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS , NULL /* unlink */ #endif }; /* Maps FT5x06 touch events into bit encoded representation used by NuttX */ static const uint8_t g_event_map[4] = { (TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID), /* FT5X06_DOWN */ (TOUCH_UP | TOUCH_ID_VALID), /* FT5X06_UP */ (TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID), /* FT5X06_CONTACT */ TOUCH_ID_VALID /* FT5X06_INVALID */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ft5x06_notify ****************************************************************************/ static void ft5x06_notify(FAR struct ft5x06_dev_s *priv) { int i; /* If there are threads waiting on poll() for FT5x06 data to become * available, then wake them up now. NOTE: we wake up all waiting threads * because we do not know that they are going to do. If they all try to * read the data, then some make end up blocking after all. */ for (i = 0; i < CONFIG_FT5X06_NPOLLWAITERS; i++) { struct pollfd *fds = priv->fds[i]; if (fds) { fds->revents |= POLLIN; iinfo("Report events: %02x\n", fds->revents); nxsem_post(fds->sem); } } /* If there are threads waiting for read data, then signal one of them * that the read data is available. */ if (priv->nwaiters > 0) { /* After posting this semaphore, we need to exit because the FT5x06 * is no longer available. */ nxsem_post(&priv->waitsem); } } /**************************************************************************** * Name: ft5x06_data_worker ****************************************************************************/ static void ft5x06_data_worker(FAR void *arg) { FAR struct ft5x06_dev_s *priv = (FAR struct ft5x06_dev_s *)arg; FAR const struct ft5x06_config_s *config; FAR struct ft5x06_touch_data_s *sample; struct i2c_msg_s msg[2]; uint8_t regaddr; int ret; /* Get a pointer the callbacks for convenience */ DEBUGASSERT(priv != NULL && priv->config != NULL); config = priv->config; /* We need to have exclusive access to the touchbuf so that we do not * corrupt any read operation that is in place. */ do { ret = nxsem_wait_uninterruptible(&priv->devsem); /* This would only fail if something canceled the worker thread? * That is not expected. */ DEBUGASSERT(ret == OK || ret == -ECANCELED); } while (ret < 0); /* Read touch data */ /* Set up the address write operation */ regaddr = FT5X06_TOUCH_DATA_STARTREG; msg[0].frequency = priv->frequency; /* I2C frequency */ msg[0].addr = config->address; /* 7-bit address */ msg[0].flags = 0; /* Write transaction with START */ msg[0].buffer = ®addr; /* Send one byte of data (no STOP) */ msg[0].length = 1; /* Set up the data read operation. * * REVISIT: If CONFIG_FT5X06_SINGLEPOINT is selected, could we not just * set the length for one sample? Or is there some reason why we have to * read all of the points? */ msg[1].frequency = priv->frequency; /* I2C frequency */ msg[1].addr = config->address; /* 7-bit address */ msg[1].flags = I2C_M_READ; /* Read transaction with Re-START */ msg[1].buffer = priv->touchbuf; /* Read all touch data */ msg[1].length = FT5X06_TOUCH_DATA_LEN; ret = I2C_TRANSFER(priv->i2c, msg, 2); if (ret >= 0) { /* In polled mode, we may read invalid touch data. If there is * no touch data, the FT5x06 returns all 0xff the very first time. * After that, it returns the same old stale data when there is * no touch data. */ sample = (FAR struct ft5x06_touch_data_s *)priv->touchbuf; /* Notify waiters (only if we ready some valid data). * * REVISIT: For improved performance consider moving the duplicate * report and thresholding logic from ft5x06_sample() to here. That * would save a context switch. */ if (sample->tdstatus <= FT5X06_MAX_TOUCHES) { /* Notify any waiters that new FT5x06 data is available */ priv->valid = true; ft5x06_notify(priv); } #ifdef CONFIG_FT5X06_POLLMODE /* Update the poll rate */ if (sample->tdstatus > 0 && sample->tdstatus <= FT5X06_MAX_TOUCHES) { /* Keep it at the minimum if touches are detected. */ priv->delay = POLL_MINDELAY; } else if (priv->delay < POLL_MAXDELAY) { /* Otherwise, let the poll rate rise gradually up to the maximum * if there is no touch. */ priv->delay += POLL_INCREMENT; } #endif } #ifdef CONFIG_FT5X06_POLLMODE /* Exit, re-starting the poll. */ wd_start(&priv->polltimer, priv->delay, ft5x06_poll_timeout, (wdparm_t)priv); #else /* Exit, re-enabling FT5x06 interrupts */ config->enable(config, true); #endif nxsem_post(&priv->devsem); } /**************************************************************************** * Name: ft5x06_poll_timeout ****************************************************************************/ #ifdef CONFIG_FT5X06_POLLMODE static void ft5x06_poll_timeout(wdparm_t arg) { FAR struct ft5x06_dev_s *priv = (FAR struct ft5x06_dev_s *)arg; int ret; /* Transfer processing to the worker thread. Since FT5x06 poll timer is * disabled while the work is pending, no special action should be * required to protected the work queue. */ DEBUGASSERT(priv->work.worker == NULL); ret = work_queue(HPWORK, &priv->work, ft5x06_data_worker, priv, 0); if (ret != 0) { ierr("ERROR: Failed to queue work: %d\n", ret); } } #endif /**************************************************************************** * Name: ft5x06_data_interrupt ****************************************************************************/ #ifndef CONFIG_FT5X06_POLLMODE static int ft5x06_data_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct ft5x06_dev_s *priv = (FAR struct ft5x06_dev_s *)arg; FAR const struct ft5x06_config_s *config; int ret; /* Get a pointer the callbacks for convenience (and so the code is not so * ugly). */ config = priv->config; DEBUGASSERT(config != NULL); /* Disable further interrupts */ config->enable(config, false); /* Transfer processing to the worker thread. Since FT5x06 interrupts are * disabled while the work is pending, no special action should be required * to protected the work queue. */ DEBUGASSERT(priv->work.worker == NULL); ret = work_queue(HPWORK, &priv->work, ft5x06_data_worker, priv, 0); if (ret != 0) { ierr("ERROR: Failed to queue work: %d\n", ret); } /* Clear any pending interrupts and return success */ config->clear(config); return OK; } #endif /**************************************************************************** * Name: ft5x06_sample ****************************************************************************/ #ifdef CONFIG_FT5X06_SINGLEPOINT static ssize_t ft5x06_sample(FAR struct ft5x06_dev_s *priv, FAR char *buffer, size_t len) { FAR struct ft5x06_touch_data_s *raw; FAR struct ft5x06_touch_point_s *touch; FAR struct touch_sample_s *sample; FAR struct touch_point_s *point; int16_t x; int16_t y; uint8_t event; uint8_t id; if (!priv->valid) { return 0; /* Nothing to read */ } /* Raw data pointers (source) */ raw = (FAR struct ft5x06_touch_data_s *)priv->touchbuf; touch = raw->touch; /* Get the reported X and Y positions */ #ifdef CONFIG_FT5X06_SWAPXY y = TOUCH_POINT_GET_X(touch[0]); x = TOUCH_POINT_GET_Y(touch[0]); #else x = TOUCH_POINT_GET_X(touch[0]); y = TOUCH_POINT_GET_Y(touch[0]); #endif /* Get the touch point ID and event */ event = TOUCH_POINT_GET_EVENT(touch[0]); id = TOUCH_POINT_GET_ID(touch[0]); if (event == FT5X06_INVALID) { priv->lastevent = FT5X06_INVALID; goto reset_and_drop; } if (id == priv->lastid && event == priv->lastevent) { /* Same ID and event.. Is there positional data? */ if (raw->tdstatus == 0 || event == FT5X06_UP) { /* No... no new touch data */ goto reset_and_drop; } else { int16_t deltax; int16_t deltay; /* Compare the change in position from the last report. */ deltax = (x - priv->lastx); if (deltax < 0) { deltax = -deltax; } if (deltax < CONFIG_FT5X06_THRESHX) { /* There as been no significant change in X, try Y */ deltay = (y - priv->lasty); if (deltay < 0) { deltay = -deltay; } if (deltax < CONFIG_FT5X06_THRESHX) { /* Ignore... no significant change in Y either */ goto drop; } } } } priv->lastid = id; priv->lastevent = event; priv->lastx = x; priv->lasty = y; /* User data buffer points (sink) */ /* Return the number of touches read */ sample = (FAR struct touch_sample_s *)buffer; sample->npoints = 1; /* Decode and return the single touch point */ point = sample->point; point[0].id = id; point[0].flags = g_event_map[event]; point[0].x = x; point[0].y = y; point[0].h = 0; point[0].w = 0; point[0].pressure = 0; priv->valid = false; return SIZEOF_TOUCH_SAMPLE_S(1); reset_and_drop: priv->lastx = 0; priv->lasty = 0; drop: priv->valid = false; return 0; /* No new touches read. */ } #else static ssize_t ft5x06_sample(FAR struct ft5x06_dev_s *priv, FAR char *buffer, size_t len) { FAR struct ft5x06_touch_data_s *raw; FAR struct ft5x06_touch_point_s *touch; FAR struct touch_sample_s *sample; FAR struct touch_point_s *point; unsigned int maxtouches; unsigned int ntouches; int i; maxtouches = (len - sizeof(int)) / sizeof(struct touch_point_s); DEBUGASSERT(maxtouches > 0); /* Already verified */ if (!priv->valid) { return 0; /* Nothing to read */ } /* Raw data pointers (source) */ raw = (FAR struct ft5x06_touch_data_s *)priv->touchbuf; touch = raw->touch; /* Decode number of touches */ ntouches = raw->tdstatus; DEBUGASSERT(ntouches <= FT5X06_MAX_TOUCHES); if (ntouches > maxtouches) { ntouches = maxtouches; } if (ntouches < 1) { priv->valid = false; return 0; /* No touches read. */ } /* User data buffer points (sink) */ sample = (FAR struct touch_sample_s *)buffer; point = sample->point; /* Return the number of touches read */ sample->npoints = ntouches; /* Decode and return the touch points */ for (i = 0; i < ntouches; i++) { int event = TOUCH_POINT_GET_EVENT(touch[i]); point[i].id = TOUCH_POINT_GET_ID(touch[i]); point[i].flags = g_event_map[event]; #ifdef CONFIG_FT5X06_SWAPXY point[i].y = TOUCH_POINT_GET_X(touch[i]); point[i].x = TOUCH_POINT_GET_Y(touch[i]); #else point[i].x = TOUCH_POINT_GET_X(touch[i]); point[i].y = TOUCH_POINT_GET_Y(touch[i]); #endif point[i].h = 0; point[i].w = 0; point[i].pressure = 0; } priv->valid = false; return SIZEOF_TOUCH_SAMPLE_S(ntouches); } #endif /* CONFIG_FT5X06_SINGLEPOINT */ /**************************************************************************** * Name: ft5x06_waitsample ****************************************************************************/ static ssize_t ft5x06_waitsample(FAR struct ft5x06_dev_s *priv, FAR char *buffer, size_t len) { int ret; /* Disable pre-emption to prevent other threads from getting control while * we muck with the semaphores. */ sched_lock(); /* Now release the semaphore that manages mutually exclusive access to * the device structure. This may cause other tasks to become ready to * run, but they cannot run yet because pre-emption is disabled. */ nxsem_post(&priv->devsem); /* Try to get the a sample... if we cannot, then wait on the semaphore * that is posted when new sample data is available. */ while (!priv->valid) { /* Increment the count of waiters */ priv->nwaiters++; /* Wait for a change in the FT5x06 state */ ret = nxsem_wait(&priv->waitsem); priv->nwaiters--; if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); goto errout; } } /* Re-acquire the semaphore that manages mutually exclusive access to * the device structure. We may have to wait here. But we have our * sample. Interrupts and pre-emption will be re-enabled while we wait. */ ret = nxsem_wait(&priv->devsem); if (ret >= 0) { /* Now sample the data. * * REVISIT: Is it safe to assume that priv->valid will always be * true? I think that sched_lock() would protect the setting. */ ret = ft5x06_sample(priv, buffer, len); } errout: /* Restore pre-emption. We might get suspended here but that is okay * because we already have our sample. Note: this means that if there * were two threads reading from the FT5x06 for some reason, the data * might be read out of order. */ sched_unlock(); return ret; } /**************************************************************************** * Name: ft5x06_bringup ****************************************************************************/ static int ft5x06_bringup(FAR struct ft5x06_dev_s *priv) { FAR const struct ft5x06_config_s *config; struct i2c_msg_s msg; uint8_t data[2]; int ret; /* Get a pointer the callbacks for convenience (and so the code is not so * ugly). */ config = priv->config; DEBUGASSERT(config != NULL); /* Set device mode to normal operation */ data[0] = FT5X06_TOUCH_MODE_REG; /* Register address */ data[1] = FT5X06_DEV_MODE_WORKING; /* Normal mode */ msg.frequency = priv->frequency; /* I2C frequency */ msg.addr = config->address; /* 7-bit address */ msg.flags = 0; /* Write transaction with START */ msg.buffer = data; /* Send two bytes followed by STOP */ msg.length = 2; ret = I2C_TRANSFER(priv->i2c, &msg, 1); if (ret < 0) { return ret; } #ifndef CONFIG_FT5X06_POLLMODE /* Enable FT5x06 interrupts */ config->clear(config); config->enable(config, true); #endif return OK; } /**************************************************************************** * Name: ft5x06_shutdown ****************************************************************************/ static void ft5x06_shutdown(FAR struct ft5x06_dev_s *priv) { #ifdef CONFIG_FT5X06_POLLMODE /* Stop the poll timer */ wd_cancel(&priv->polltimer); #else FAR const struct ft5x06_config_s *config = priv->config; /* Make sure that the FT5x06 interrupt is disabled */ config->clear(config); config->enable(config, false); #endif } /**************************************************************************** * Name: ft5x06_open ****************************************************************************/ static int ft5x06_open(FAR struct file *filep) { FAR struct inode *inode; FAR struct ft5x06_dev_s *priv; uint8_t tmp; int ret; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct ft5x06_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = nxsem_wait(&priv->devsem); if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Increment the reference count */ tmp = priv->crefs + 1; if (tmp == 0) { /* More than 255 opens; uint8_t overflows to zero */ ret = -EMFILE; goto errout_with_sem; } /* When the reference increments to 1, this is the first open event * on the driver.. and the time when we must initialize the driver. */ if (tmp == 1) { ret = ft5x06_bringup(priv); if (ret < 0) { ierr("ERROR: ft5x06_bringup failed: %d\n", ret); goto errout_with_sem; } } /* Save the new open count on success */ priv->crefs = tmp; errout_with_sem: nxsem_post(&priv->devsem); return ret; } /**************************************************************************** * Name: ft5x06_close ****************************************************************************/ static int ft5x06_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct ft5x06_dev_s *priv; int ret; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct ft5x06_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = nxsem_wait(&priv->devsem); if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Decrement the reference count unless it would decrement a negative * value. */ if (priv->crefs >= 1) { priv->crefs--; } /* When the count decrements to zero, there are no further open references * to the driver and it can be uninitialized. */ if (priv->crefs == 0) { ft5x06_shutdown(priv); } nxsem_post(&priv->devsem); return OK; } /**************************************************************************** * Name: ft5x06_read ****************************************************************************/ static ssize_t ft5x06_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct inode *inode; FAR struct ft5x06_dev_s *priv; int ret; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct ft5x06_dev_s *)inode->i_private; /* Verify that the caller has provided a buffer large enough to receive * the touch data. */ if (len < SIZEOF_TOUCH_SAMPLE_S(1)) { /* We could provide logic to break up a touch report into segments and * handle smaller reads... but why? */ return -ENOSYS; } /* Get exclusive access to the driver data structure */ ret = nxsem_wait(&priv->devsem); if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Try to read sample data. */ ret = ft5x06_sample(priv, buffer, len); while (ret == 0) { /* Sample data is not available now. We would have to wait to receive * sample data. If the user has specified the O_NONBLOCK option, then * just return an error. */ if (filep->f_oflags & O_NONBLOCK) { ret = -EAGAIN; goto errout; } /* Wait for sample data */ ret = ft5x06_waitsample(priv, buffer, len); if (ret < 0) { /* We might have been awakened by a signal */ goto errout; } } ret = SIZEOF_TOUCH_SAMPLE_S(1); errout: nxsem_post(&priv->devsem); return ret; } /**************************************************************************** * Name: ft5x06_ioctl ****************************************************************************/ static int ft5x06_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct ft5x06_dev_s *priv; int ret; iinfo("cmd: %d arg: %ld\n", cmd, arg); DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct ft5x06_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = nxsem_wait(&priv->devsem); if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } /* Process the IOCTL by command */ switch (cmd) { case TSIOC_SETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ { FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); DEBUGASSERT(priv->config != NULL && ptr != NULL); priv->frequency = *ptr; } break; case TSIOC_GETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ { FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); DEBUGASSERT(priv->config != NULL && ptr != NULL); *ptr = priv->frequency; } break; default: ret = -ENOTTY; break; } nxsem_post(&priv->devsem); return ret; } /**************************************************************************** * Name: ft5x06_poll ****************************************************************************/ static int ft5x06_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { FAR struct inode *inode; FAR struct ft5x06_dev_s *priv; int ret; int i; iinfo("setup: %d\n", (int)setup); DEBUGASSERT(filep && fds); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct ft5x06_dev_s *)inode->i_private; /* Are we setting up the poll? Or tearing it down? */ ret = nxsem_wait(&priv->devsem); if (ret < 0) { ierr("ERROR: nxsem_wait failed: %d\n", ret); return ret; } if (setup) { /* Ignore waits that do not include POLLIN */ if ((fds->events & POLLIN) == 0) { ierr("ERROR: Missing POLLIN: revents: %08x\n", fds->revents); ret = -EDEADLK; goto errout; } /* This is a request to set up the poll. Find an available * slot for the poll structure reference */ for (i = 0; i < CONFIG_FT5X06_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_FT5X06_NPOLLWAITERS) { ierr("ERROR: No available slot found: %d\n", i); fds->priv = NULL; ret = -EBUSY; goto errout; } /* Should we immediately notify on any of the requested events? */ if (priv->valid) { ft5x06_notify(priv); } } else if (fds->priv) { /* This is a request to tear down the poll. */ struct pollfd **slot = (struct pollfd **)fds->priv; DEBUGASSERT(slot != NULL); /* Remove all memory of the poll setup */ *slot = NULL; fds->priv = NULL; } errout: nxsem_post(&priv->devsem); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ft5x06_register * * Description: * Configure the FT5x06 to use the provided I2C device instance. This * will register the driver as /dev/inputN where N is the minor device * number * * Input Parameters: * dev - An I2C driver instance * config - Persistent board configuration data * minor - The input device minor number * * Returned Value: * Zero is returned on success. Otherwise, a negated errno value is * returned to indicate the nature of the failure. * ****************************************************************************/ int ft5x06_register(FAR struct i2c_master_s *i2c, FAR const struct ft5x06_config_s *config, int minor) { FAR struct ft5x06_dev_s *priv; char devname[DEV_NAMELEN]; int ret; iinfo("i2c: %p minor: %d\n", i2c, minor); /* Debug-only sanity checks */ DEBUGASSERT(i2c != NULL && config != NULL && minor >= 0 && minor < 100); #ifdef CONFIG_FT5X06_POLLMODE DEBUGASSERT(config->wakeup != NULL && config->nreset != NULL); #else DEBUGASSERT(config->attach != NULL && config->enable != NULL && config->clear != NULL && config->wakeup != NULL && config->nreset != NULL); #endif /* Create and initialize a FT5x06 device driver instance */ priv = (FAR struct ft5x06_dev_s *)kmm_zalloc(sizeof(struct ft5x06_dev_s)); if (!priv) { ierr("ERROR: kmm_zalloc(%d) failed\n", sizeof(struct ft5x06_dev_s)); return -ENOMEM; } /* Initialize the FT5x06 device driver instance */ priv->i2c = i2c; /* Save the I2C device handle */ priv->config = config; /* Save the board configuration */ priv->frequency = config->frequency; /* Set the current I2C frequency */ nxsem_init(&priv->devsem, 0, 1); /* Initialize device structure semaphore */ nxsem_init(&priv->waitsem, 0, 0); /* Initialize pen event wait semaphore */ /* The event wait semaphore is used for signaling and, hence, should not * have priority inheritance enabled. */ nxsem_set_protocol(&priv->waitsem, SEM_PRIO_NONE); #ifdef CONFIG_FT5X06_POLLMODE /* Allocate a timer for polling the FT5x06 */ priv->delay = POLL_MAXDELAY; #else /* Make sure that the FT5x06 interrupt interrupt is disabled */ config->clear(config); config->enable(config, false); /* Attach the interrupt handler */ ret = config->attach(config, ft5x06_data_interrupt, priv); if (ret < 0) { ierr("ERROR: Failed to attach interrupt\n"); goto errout_with_priv; } #endif /* Register the device as an input device */ snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor); iinfo("Registering %s\n", devname); ret = register_driver(devname, &ft5x06_fops, 0666, priv); if (ret < 0) { ierr("ERROR: register_driver() failed: %d\n", ret); goto errout_with_priv; } /* Schedule work to perform the initial sampling and to set the data * availability conditions. */ ret = work_queue(HPWORK, &priv->work, ft5x06_data_worker, priv, 0); if (ret < 0) { ierr("ERROR: Failed to queue work: %d\n", ret); goto errout_with_priv; } /* And return success */ return OK; errout_with_priv: nxsem_destroy(&priv->devsem); kmm_free(priv); return ret; }