1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228 |
- /****************************************************************************
- * drivers/input/ft5x06.c
- *
- * Copyright (C) 2017 Gregory Nutt. All rights reserved.
- * Author: Gregory Nutt <gnutt@nuttx.org>
- *
- * 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 <nuttx/config.h>
- #include <sys/types.h>
- #include <stdbool.h>
- #include <stdio.h>
- #include <unistd.h>
- #include <string.h>
- #include <fcntl.h>
- #include <poll.h>
- #include <errno.h>
- #include <assert.h>
- #include <debug.h>
- #include <nuttx/irq.h>
- #include <nuttx/kmalloc.h>
- #include <nuttx/arch.h>
- #include <nuttx/fs/fs.h>
- #include <nuttx/i2c/i2c_master.h>
- #include <nuttx/semaphore.h>
- #include <nuttx/wqueue.h>
- #include <nuttx/wdog.h>
- #include <nuttx/input/touchscreen.h>
- #include <nuttx/input/ft5x06.h>
- #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;
- }
|