nuttx/drivers/input/tsc2007.c
dongjiuzhu d452a05910 pollnotify: we should send poll events before semaphore incrementes.
There is a good case on sim platform:
When we input some cmd and click enter key to start application in terminal,
this context will change to application from IDLE loop. Althrough entey key '\r'
has been received to recv buffer and complete post semaphore of reader, but
pollnotify may not be called because context change. So when application run
poll function, because no events happend and poll enter wait, context will
again change to IDLE loop, this pollnotify of IDLE loop will run to send poll
events, poll function of applicaton will wake up. It's wrong!

Change-Id: I812a889f2e90781a9c3cb4b0251cccc4d32bebd1
Signed-off-by: dongjiuzhu <dongjiuzhu1@xiaomi.com>
2020-10-26 08:27:09 -03:00

1310 lines
41 KiB
C

/****************************************************************************
* drivers/input/tsc2007.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.
*
****************************************************************************/
/* The TSC2007 is an analog interface circuit for a human interface touch
* screen device. All peripheral functions are controlled through the command
* byte and onboard state machines.
*
* References:
* "1.2V to 3.6V, 12-Bit, Nanopower, 4-Wire Micro TOUCH SCREEN CONTROLLER
* with I2C Interface," SBAS405A March 2007, Revised, March 2009, Texas
* Instruments Incorporated
*/
/****************************************************************************
* 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/random.h>
#include <nuttx/signal.h>
#include <nuttx/input/touchscreen.h>
#include <nuttx/input/tsc2007.h>
#include "tsc2007.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
/* Reference counting is partially implemented, but not needed in the
* current design.
*/
#undef CONFIG_TSC2007_REFCNT
/* I don't think that it is necessary to activate the converters before
* making measurements. However, I will keep this functionality enabled
* until I have a change to prove that that activation is unnecessary.
*/
#undef CONFIG_TSC2007_ACTIVATE
#define CONFIG_TSC2007_ACTIVATE 1
/* 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
/* Commands *****************************************************************/
#define TSC2007_SETUP (TSC2007_CMD_FUNC_SETUP)
#ifdef CONFIG_TSC2007_8BIT
# define TSC2007_ACTIVATE_Y (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YON)
# define TSC2007_MEASURE_Y (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YPOS)
# define TSC2007_ACTIVATE_X (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XON)
# define TSC2007_MEASURE_X (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XPOS)
# define TSC2007_ACTIVATE_Z (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YXON)
# define TSC2007_MEASURE_Z1 (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z1POS)
# define TSC2007_MEASURE_Z2 (TSC2007_CMD_8BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z2POS)
# define TSC2007_ENABLE_PENIRQ (TSC2007_CMD_8BIT | TSC2007_CMD_PWRDN_IRQEN)
#else
# define TSC2007_ACTIVATE_Y (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YON)
# define TSC2007_MEASURE_Y (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YPOS)
# define TSC2007_ACTIVATE_X (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XON)
# define TSC2007_MEASURE_X (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_XPOS)
# define TSC2007_ACTIVATE_Z (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_YXON)
# define TSC2007_MEASURE_Z1 (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z1POS)
# define TSC2007_MEASURE_Z2 (TSC2007_CMD_12BIT | TSC2007_CMD_ADCON_IRQDIS | TSC2007_CMD_FUNC_Z2POS)
# define TSC2007_ENABLE_PENIRQ (TSC2007_CMD_12BIT | TSC2007_CMD_PWRDN_IRQEN)
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* This describes the state of one contact */
enum tsc2007_contact_e
{
CONTACT_NONE = 0, /* No contact */
CONTACT_DOWN, /* First contact */
CONTACT_MOVE, /* Same contact, possibly different position */
CONTACT_UP, /* Contact lost */
};
/* This structure describes the results of one TSC2007 sample */
struct tsc2007_sample_s
{
uint8_t id; /* Sampled touch point ID */
uint8_t contact; /* Contact state (see enum tsc2007_contact_e) */
bool valid; /* True: x,y,pressure contain valid, sampled data */
uint16_t x; /* Measured X position */
uint16_t y; /* Measured Y position */
uint16_t pressure; /* Calculated pressure */
};
/* This structure describes the state of one TSC2007 driver instance */
struct tsc2007_dev_s
{
#ifdef CONFIG_TSC2007_MULTIPLE
FAR struct tsc2007_dev_s *flink; /* Supports a singly linked list of drivers */
#endif
#ifdef CONFIG_TSC2007_REFCNT
uint8_t crefs; /* Number of times the device has been opened */
#endif
uint8_t nwaiters; /* Number of threads waiting for TSC2007 data */
uint8_t id; /* Current touch point ID */
volatile bool penchange; /* An unreported event is buffered */
sem_t devsem; /* Manages exclusive access to this structure */
sem_t waitsem; /* Used to wait for the availability of data */
FAR struct tsc2007_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" */
struct tsc2007_sample_s sample; /* Last sampled touch point 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_TSC2007_NPOLLWAITERS];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static void tsc2007_notify(FAR struct tsc2007_dev_s *priv);
static int tsc2007_sample(FAR struct tsc2007_dev_s *priv,
FAR struct tsc2007_sample_s *sample);
static int tsc2007_waitsample(FAR struct tsc2007_dev_s *priv,
FAR struct tsc2007_sample_s *sample);
#ifdef CONFIG_TSC2007_ACTIVATE
static int tsc2007_activate(FAR struct tsc2007_dev_s *priv, uint8_t cmd);
#endif
static int tsc2007_transfer(FAR struct tsc2007_dev_s *priv, uint8_t cmd);
static void tsc2007_worker(FAR void *arg);
static int tsc2007_interrupt(int irq, FAR void *context);
/* Character driver methods */
static int tsc2007_open(FAR struct file *filep);
static int tsc2007_close(FAR struct file *filep);
static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer,
size_t len);
static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int tsc2007_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 tsc2007_fops =
{
tsc2007_open, /* open */
tsc2007_close, /* close */
tsc2007_read, /* read */
0, /* write */
0, /* seek */
tsc2007_ioctl, /* ioctl */
tsc2007_poll /* poll */
};
/* If only a single TSC2007 device is supported, then the driver state
* structure may as well be pre-allocated.
*/
#ifndef CONFIG_TSC2007_MULTIPLE
static struct tsc2007_dev_s g_tsc2007;
/* Otherwise, we will need to maintain allocated driver instances in a list */
#else
static struct tsc2007_dev_s *g_tsc2007list;
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: tsc2007_notify
****************************************************************************/
static void tsc2007_notify(FAR struct tsc2007_dev_s *priv)
{
int i;
/* If there are threads waiting on poll() for TSC2007 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_TSC2007_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 TSC2007
* is no longer available.
*/
nxsem_post(&priv->waitsem);
}
}
/****************************************************************************
* Name: tsc2007_sample
****************************************************************************/
static int tsc2007_sample(FAR struct tsc2007_dev_s *priv,
FAR struct tsc2007_sample_s *sample)
{
irqstate_t flags;
int ret = -EAGAIN;
/* Interrupts me be disabled when this is called to (1) prevent posting
* of semaphores from interrupt handlers, and (2) to prevent sampled data
* from changing until it has been reported.
*/
flags = enter_critical_section();
/* Is there new TSC2007 sample data available? */
if (priv->penchange)
{
/* Yes.. the state has changed in some way. Return a copy of the
* sampled data.
*/
memcpy(sample, &priv->sample, sizeof(struct tsc2007_sample_s));
/* Now manage state transitions */
if (sample->contact == CONTACT_UP)
{
/* Next.. no contact. Increment the ID so that next contact ID
* will be unique. X/Y positions are no longer valid.
*/
priv->sample.contact = CONTACT_NONE;
priv->sample.valid = false;
priv->id++;
}
else if (sample->contact == CONTACT_DOWN)
{
/* First report -- next report will be a movement */
priv->sample.contact = CONTACT_MOVE;
}
priv->penchange = false;
ret = OK;
}
leave_critical_section(flags);
return ret;
}
/****************************************************************************
* Name: tsc2007_waitsample
****************************************************************************/
static int tsc2007_waitsample(FAR struct tsc2007_dev_s *priv,
FAR struct tsc2007_sample_s *sample)
{
irqstate_t flags;
int ret;
/* Interrupts me be disabled when this is called to (1) prevent posting
* of semaphores from interrupt handlers, and (2) to prevent sampled data
* from changing until it has been reported.
*
* In addition, we will also disable pre-emption to prevent other threads
* from getting control while we muck with the semaphores.
*/
sched_lock();
flags = enter_critical_section();
/* 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 (tsc2007_sample(priv, sample) < 0)
{
/* Wait for a change in the TSC2007 state */
priv->nwaiters++;
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);
errout:
/* Then re-enable interrupts. We might get interrupt here and there
* could be a new sample. But no new threads will run because we still
* have pre-emption disabled.
*/
leave_critical_section(flags);
/* 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 TSC2007 for some reason, the data
* might be read out of order.
*/
sched_unlock();
return ret;
}
/****************************************************************************
* Name: tsc2007_activate
****************************************************************************/
#ifdef CONFIG_TSC2007_ACTIVATE
static int tsc2007_activate(FAR struct tsc2007_dev_s *priv, uint8_t cmd)
{
struct i2c_msg_s msg;
uint8_t data;
int ret;
/* Send the setup command (with no ACK) followed by the A/D converter
* activation command (ACKed).
*/
data = TSC2007_SETUP;
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 = &data; /* Transfer from this address */
msg.length = 1; /* Send one byte following the address */
/* Ignore errors from the setup command (because it is not ACKed) */
I2C_TRANSFER(priv->i2c, &msg, 1);
/* Now activate the A/D converter */
data = cmd;
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 = &data; /* Transfer from this address */
msg.length = 1; /* Send one byte following the address */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
}
return ret;
}
#else
# define tsc2007_activate(p,c)
#endif
/****************************************************************************
* Name: tsc2007_transfer
****************************************************************************/
static int tsc2007_transfer(FAR struct tsc2007_dev_s *priv, uint8_t cmd)
{
struct i2c_msg_s msg;
uint8_t data12[2];
int ret;
/* "A conversion/write cycle begins when the master issues the address
* byte containing the slave address of the TSC2007, with the eighth bit
* equal to a 0 (R/W = 0)... Once the eighth bit has been received...
* the TSC2007 issues an acknowledge.
*
* "When the master receives the acknowledge bit from the TSC2007, the
* master writes the command byte to the slave... After the command byte
* is received by the slave, the slave issues another acknowledge bit.
* The master then ends the write cycle by issuing a repeated START or a
* STOP condition...
*/
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 = &cmd; /* Transfer from this address */
msg.length = 1; /* Send one byte following the address */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
return ret;
}
/* "The input multiplexer channel for the A/D converter is selected when
* bits C3 through C0 are clocked in. If the selected channel is an X-,Y-,
* or Z-position measurement, the appropriate drivers turn on once the
* acquisition period begins.
*
* "... the input sample acquisition period starts on the falling edge of
* SCL when the C0 bit of the command byte has been latched, and ends
* when a STOP or repeated START condition has been issued. A/D conversion
* starts immediately after the acquisition period...
*
* "For best performance, the I2C bus should remain in an idle state while
* an A/D conversion is taking place. ... The master should wait for at
* least 10ms before attempting to read data from the TSC2007...
*/
nxsig_usleep(10 * 1000);
/* "Data access begins with the master issuing a START condition followed
* by the address byte ... with R/W = 1.
*
* "When the eighth bit has been received and the address matches, the
* slave issues an acknowledge. The first byte of serial data then follows
* (D11-D4, MSB first).
*
* "After the first byte has been sent by the slave, it releases the SDA
* line for the master to issue an acknowledge. The slave responds with
* the second byte of serial data upon receiving the acknowledge from the
* master (D3-D0, followed by four 0 bits). The second byte is followed by
* a NOT acknowledge bit (ACK = 1) from the master to indicate that the
* last data byte has been received...
*/
msg.frequency = priv->config->frequency; /* I2C frequency */
msg.addr = priv->config->address; /* 7-bit address */
msg.flags = I2C_M_READ; /* Read transaction, beginning with START */
msg.buffer = data12; /* Transfer to this address */
msg.length = 2; /* Read two bytes following the address */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
ierr("ERROR: I2C_TRANSFER failed: %d\n", ret);
return ret;
}
/* Get the MS 8 bits from the first byte and the remaining LS 4 bits from
* the second byte. The valid range of data is then from 0 to 4095 with
* the LSB unit corresponding to Vref/4096.
*/
ret = (unsigned int)data12[0] << 4 | (unsigned int)data12[1] >> 4;
iinfo("data: 0x%04x\n", ret);
return ret;
}
/****************************************************************************
* Name: tsc2007_worker
****************************************************************************/
static void tsc2007_worker(FAR void *arg)
{
FAR struct tsc2007_dev_s *priv = (FAR struct tsc2007_dev_s *)arg;
FAR struct tsc2007_config_s *config; /* Convenience pointer */
bool pendown; /* true: pend is down */
uint16_t x; /* X position */
uint16_t y; /* Y position */
uint16_t z1; /* Z1 position */
uint16_t z2; /* Z2 position */
uint32_t pressure; /* Measured pressure */
DEBUGASSERT(priv != NULL);
/* Get a pointer the callbacks for convenience (and so the code is not so
* ugly).
*/
config = priv->config;
DEBUGASSERT(config != NULL);
/* Check for pen up or down by reading the PENIRQ GPIO. */
pendown = config->pendown(config);
/* Handle the change from pen down to pen up */
if (!pendown)
{
/* Ignore the interrupt if the pen was already down (CONTACT_NONE ==
* pen up and already reported. CONTACT_UP == pen up, but not
* reported)
*/
if (priv->sample.contact == CONTACT_NONE)
{
goto errout;
}
}
/* It is a pen down event. If the last loss-of-contact event has not been
* processed yet, then we have to ignore the pen down event (or else it
* will look like a drag event)
*/
else if (priv->sample.contact == CONTACT_UP)
{
goto errout;
}
else
{
/* Handle all pen down events. First, sample X, Y, Z1, and Z2 values.
*
* "A resistive touch screen operates by applying a voltage across a
* resistor network and measuring the change in resistance at a given
* point on the matrix where the screen is touched by an input
* (stylus, pen, or finger). The change in the resistance ratio marks
* the location on the touch screen.
*
* "The 4-wire touch screen panel works by applying a voltage across
* the vertical or horizontal resistive network. The A/D converter
* converts the voltage measured at the point where the panel is
* touched. A measurement of the Y position of the pointing device is
* made by connecting the X+ input to a data converter chip, turning
* on the Y+ and Y- drivers, and digitizing the voltage seen at the
* X+ input ..."
*
* "... it is recommended that whenever the host writes to the TSC2007,
* the master processor masks the interrupt associated to PENIRQ.
* This masking prevents false triggering of interrupts when the
* PENIRQ line is disabled in the cases previously listed."
*/
tsc2007_activate(priv, TSC2007_ACTIVATE_X);
y = tsc2007_transfer(priv, TSC2007_MEASURE_Y);
/* "Voltage is then applied to the other axis, and the A/D converter
* converts the voltage representing the X position on the screen.
* This process provides the X and Y coordinates to the associated
* processor."
*/
tsc2007_activate(priv, TSC2007_ACTIVATE_Y);
x = tsc2007_transfer(priv, TSC2007_MEASURE_X);
/* "... To determine pen or finger touch, the pressure of the touch
* must be determined. ... There are several different ways of
* performing this measurement. The TSC2007 supports two methods. The
* first method requires knowing the X-plate resistance, the
* measurement of the X-position, and two additional cross panel
* measurements (Z2 and Z1) of the touch screen."
*
* Rtouch = Rxplate * (X / 4096)* (Z2/Z1 - 1)
*
* "The second method requires knowing both the X-plate and Y-plate
* resistance, measurement of X-position and Y-position, and Z1 ..."
*
* Rtouch = Rxplate * (X / 4096) * (4096/Z1 - 1) -
* Ryplate * (1 - Y/4096)
*
* Read Z1 and Z2 values.
*/
tsc2007_activate(priv, TSC2007_ACTIVATE_Z);
z1 = tsc2007_transfer(priv, TSC2007_MEASURE_Z1);
tsc2007_activate(priv, TSC2007_ACTIVATE_Z);
z2 = tsc2007_transfer(priv, TSC2007_MEASURE_Z2);
/* Power down ADC and enable PENIRQ */
tsc2007_transfer(priv, TSC2007_ENABLE_PENIRQ);
/* Now calculate the pressure using the first method, reduced to:
*
* Rtouch = X * Rxplate *(Z2 - Z1) * / Z1 / 4096
*/
if (z1 == 0)
{
ierr("ERROR: Z1 zero\n");
pressure = 0;
}
else
{
pressure = (x * config->rxplate * (z2 - z1)) / z1;
pressure = (pressure + 2048) >> 12;
iinfo("Position: (%d,%4d) pressure: %u z1/2: (%d,%d)\n",
x, y, pressure, z1, z2);
/* Ignore out of range caculcations */
if (pressure > 0x0fff)
{
ierr("ERROR: Dropped out-of-range pressure: %d\n", pressure);
pressure = 0;
}
}
/* Save the measurements */
priv->sample.x = x;
priv->sample.y = y;
priv->sample.pressure = pressure;
priv->sample.valid = true;
add_ui_randomness((x << 16) ^ y ^ (pressure << 9));
}
/* Note the availability of new measurements */
if (pendown)
{
/* If this is the first (acknowledged) pen down report, then report
* this as the first contact. If contact == CONTACT_DOWN, it will be
* set to set to CONTACT_MOVE after the contact is first sampled.
*/
if (priv->sample.contact != CONTACT_MOVE)
{
/* First contact */
priv->sample.contact = CONTACT_DOWN;
}
}
else /* if (priv->sample.contact != CONTACT_NONE) */
{
/* The pen is up. NOTE: We know from a previous test, that this is a
* loss of contact condition. This will be changed to CONTACT_NONE
* after the loss of contact is sampled.
*/
priv->sample.contact = CONTACT_UP;
}
/* Indicate the availability of new sample data for this ID */
priv->sample.id = priv->id;
priv->penchange = true;
/* Notify any waiters that new TSC2007 data is available */
tsc2007_notify(priv);
/* Exit, re-enabling TSC2007 interrupts */
errout:
config->enable(config, true);
}
/****************************************************************************
* Name: tsc2007_interrupt
****************************************************************************/
static int tsc2007_interrupt(int irq, FAR void *context)
{
FAR struct tsc2007_dev_s *priv;
FAR struct tsc2007_config_s *config;
int ret;
/* Which TSC2007 device caused the interrupt? */
#ifndef CONFIG_TSC2007_MULTIPLE
priv = &g_tsc2007;
#else
for (priv = g_tsc2007list;
priv && priv->configs->irq != irq;
priv = priv->flink);
DEBUGASSERT(priv != NULL);
#endif
/* 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 TSC2007 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, tsc2007_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;
}
/****************************************************************************
* Name: tsc2007_open
****************************************************************************/
static int tsc2007_open(FAR struct file *filep)
{
#ifdef CONFIG_TSC2007_REFCNT
FAR struct inode *inode;
FAR struct tsc2007_dev_s *priv;
uint8_t tmp;
int ret;
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct tsc2007_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 an opportunity to do any one-time initialization.
*/
/* Save the new open count on success */
priv->crefs = tmp;
errout_with_sem:
nxsem_post(&priv->devsem);
return ret;
#else
return OK;
#endif
}
/****************************************************************************
* Name: tsc2007_close
****************************************************************************/
static int tsc2007_close(FAR struct file *filep)
{
#ifdef CONFIG_TSC2007_REFCNT
FAR struct inode *inode;
FAR struct tsc2007_dev_s *priv;
int ret;
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct tsc2007_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. When the count decrements to zero, there are no further
* open references to the driver.
*/
if (priv->crefs >= 1)
{
priv->crefs--;
}
nxsem_post(&priv->devsem);
#endif
return OK;
}
/****************************************************************************
* Name: tsc2007_read
****************************************************************************/
static ssize_t tsc2007_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct inode *inode;
FAR struct tsc2007_dev_s *priv;
FAR struct touch_sample_s *report;
struct tsc2007_sample_s sample;
int ret;
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct tsc2007_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 = tsc2007_sample(priv, &sample);
if (ret < 0)
{
/* Sample data is not available now. We would ave 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 = tsc2007_waitsample(priv, &sample);
if (ret < 0)
{
/* We might have been awakened by a signal */
goto errout;
}
}
/* In any event, we now have sampled TSC2007 data that we can report
* to the caller.
*/
report = (FAR struct touch_sample_s *)buffer;
memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(1));
report->npoints = 1;
report->point[0].id = priv->id;
report->point[0].x = sample.x;
report->point[0].y = sample.y;
report->point[0].pressure = sample.pressure;
/* Report the appropriate flags */
if (sample.contact == CONTACT_UP)
{
/* Pen is now up. Is the positional data valid? This is important to
* know because the release will be sent to the window based on its
* last positional data.
*/
if (sample.valid)
{
report->point[0].flags = TOUCH_UP | TOUCH_ID_VALID |
TOUCH_POS_VALID | TOUCH_PRESSURE_VALID;
}
else
{
report->point[0].flags = TOUCH_UP | TOUCH_ID_VALID;
}
}
else
{
if (sample.contact == CONTACT_DOWN)
{
/* Loss of contact */
report->point[0].flags = TOUCH_DOWN | TOUCH_ID_VALID |
TOUCH_POS_VALID;
}
else /* if (sample->contact == CONTACT_MOVE) */
{
/* Movement of the same contact */
report->point[0].flags = TOUCH_MOVE | TOUCH_ID_VALID |
TOUCH_POS_VALID;
}
/* A pressure measurement of zero means that pressure is not
* available.
*/
if (report->point[0].pressure != 0)
{
report->point[0].flags |= TOUCH_PRESSURE_VALID;
}
}
ret = SIZEOF_TOUCH_SAMPLE_S(1);
errout:
nxsem_post(&priv->devsem);
return ret;
}
/****************************************************************************
* Name: tsc2007_ioctl
****************************************************************************/
static int tsc2007_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode;
FAR struct tsc2007_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 tsc2007_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_SETCALIB: /* arg: Pointer to int calibration value */
{
FAR int *ptr = (FAR int *)((uintptr_t)arg);
DEBUGASSERT(priv->config != NULL && ptr != NULL);
priv->config->rxplate = *ptr;
}
break;
case TSIOC_GETCALIB: /* arg: Pointer to int calibration value */
{
FAR int *ptr = (FAR int *)((uintptr_t)arg);
DEBUGASSERT(priv->config != NULL && ptr != NULL);
*ptr = priv->config->rxplate;
}
break;
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->config->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->config->frequency;
}
break;
default:
ret = -ENOTTY;
break;
}
nxsem_post(&priv->devsem);
return ret;
}
/****************************************************************************
* Name: tsc2007_poll
****************************************************************************/
static int tsc2007_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode;
FAR struct tsc2007_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 tsc2007_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_TSC2007_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_TSC2007_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->penchange)
{
tsc2007_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
****************************************************************************/
/****************************************************************************
* Name: tsc2007_register
*
* Description:
* Configure the TSC2007 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 tsc2007_register(FAR struct i2c_master_s *dev,
FAR struct tsc2007_config_s *config, int minor)
{
FAR struct tsc2007_dev_s *priv;
char devname[DEV_NAMELEN];
#ifdef CONFIG_TSC2007_MULTIPLE
irqstate_t flags;
#endif
int ret;
iinfo("dev: %p minor: %d\n", dev, minor);
/* Debug-only sanity checks */
DEBUGASSERT(dev != NULL && config != NULL && minor >= 0 && minor < 100);
DEBUGASSERT((config->address & 0xfc) == 0x48);
DEBUGASSERT(config->attach != NULL && config->enable != NULL &&
config->clear != NULL && config->pendown != NULL);
/* Create and initialize a TSC2007 device driver instance */
#ifndef CONFIG_TSC2007_MULTIPLE
priv = &g_tsc2007;
#else
priv = (FAR struct tsc2007_dev_s *)
kmm_malloc(sizeof(struct tsc2007_dev_s));
if (!priv)
{
ierr("ERROR: kmm_malloc(%d) failed\n", sizeof(struct tsc2007_dev_s));
return -ENOMEM;
}
#endif
/* Initialize the TSC2007 device driver instance */
memset(priv, 0, sizeof(struct tsc2007_dev_s));
priv->i2c = dev; /* Save the I2C device handle */
priv->config = config; /* Save the board configuration */
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);
/* Make sure that interrupts are disabled */
config->clear(config);
config->enable(config, false);
/* Attach the interrupt handler */
ret = config->attach(config, tsc2007_interrupt);
if (ret < 0)
{
ierr("ERROR: Failed to attach interrupt\n");
goto errout_with_priv;
}
/* Power down the ADC and enable PENIRQ. This is the normal state while
* waiting for a touch event.
*/
ret = tsc2007_transfer(priv, TSC2007_ENABLE_PENIRQ);
if (ret < 0)
{
ierr("ERROR: tsc2007_transfer failed: %d\n", ret);
goto errout_with_priv;
}
/* Register the device as an input device */
snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor);
iinfo("Registering %s\n", devname);
ret = register_driver(devname, &tsc2007_fops, 0666, priv);
if (ret < 0)
{
ierr("ERROR: register_driver() failed: %d\n", ret);
goto errout_with_priv;
}
/* If multiple TSC2007 devices are supported, then we will need to add
* this new instance to a list of device instances so that it can be
* found by the interrupt handler based on the received IRQ number.
*/
#ifdef CONFIG_TSC2007_MULTIPLE
flags = enter_critical_section();
priv->flink = g_tsc2007list;
g_tsc2007list = priv;
leave_critical_section(flags);
#endif
/* Schedule work to perform the initial sampling and to set the data
* availability conditions.
*/
ret = work_queue(HPWORK, &priv->work, tsc2007_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);
#ifdef CONFIG_TSC2007_MULTIPLE
kmm_free(priv);
#endif
return ret;
}