* arch/arm/src/sama5/sam_tsd.c
*
* Copyright (C) 2013, 2016-2017 Gregory Nutt. All rights reserved.
* Authors: Gregory Nutt <gnutt@nuttx.org>
*
* The Atmel sample code has a BSD compatible license that requires this
* copyright notice:
*
* Copyright (c) 2011, Atmel Corporation
*
* 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 names NuttX nor Atmel 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.
*
****************************************************************************/
*
* SAMA5D3 Series Data Sheet
* Atmel NoOS sample code.
*/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.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/arch.h>
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
# include <nuttx/wdog.h>
#endif
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>
#include <nuttx/semaphore.h>
#include <nuttx/input/touchscreen.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "hardware/sam_adc.h"
#include "sam_adc.h"
#include "sam_tsd.h"
#if defined(CONFIG_SAMA5_ADC) && defined(CONFIG_SAMA5_TSD)
* Pre-processor Definitions
****************************************************************************/
* defined here so that it will be used consistently in all places.
*/
#define DEV_FORMAT "/dev/input%d"
#define DEV_NAMELEN 24
#define TSD_WDOG_DELAY MSEC2TICK(50)
* first pendown (but can't overflow).
*/
#define INVALID_THRESHOLD 0x1000
#ifdef CONFIG_SAMA5_TSD_4WIRE
# define TSD_ALLREADY (ADC_INT_XRDY | ADC_INT_YRDY | ADC_INT_PRDY)
#else
# define TSD_ALLREADY (ADC_INT_XRDY | ADC_INT_YRDY)
#endif
#ifdef CONFIG_SAMA5_TSD_4WIRE
# define TSD_PENUP_VALID (TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID | \
TOUCH_PRESSURE_VALID)
# define TSD_PENUP_INVALID (TOUCH_UP | TOUCH_ID_VALID)
# define TSD_PENDOWN (TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID | \
TOUCH_PRESSURE_VALID)
# define TSD_PENMOVE (TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID | \
TOUCH_PRESSURE_VALID)
#else
# define TSD_PENUP_VALID (TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID)
# define TSD_PENUP_INVALID (TOUCH_UP | TOUCH_ID_VALID)
# define TSD_PENDOWN (TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID)
# define TSD_PENMOVE (TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID)
#endif
#ifndef BOARD_TSSCTIM
# define BOARD_TSSCTIM 0
#endif
#ifndef BOARD_TSD_PENDETSENS
# define BOARD_TSD_PENDETSENS 0
#endif
* once a pen down has been detected.
* With typical periodic sample rates of 20ms+, this means some loss of
* precision of touchscreen movement detection, or even missed touches.
* A board-level #define can override this default
*/
#ifndef BOARD_TSD_PENDOWN_TSAV
# define BOARD_TSD_PENDOWN_TSAV ADC_TSMR_TSAV_8CONV
#endif
#if !defined BOARD_TSD_IBCTL && defined ATSAMA5D2
# define BOARD_TSD_IBCTL 0
#endif
#ifndef BOARD_TOUCHSCREEN_SAMPLE_CACHES
# define BOARD_TOUCHSCREEN_SAMPLE_CACHES 64
#endif
* Private Types
****************************************************************************/
enum sam_contact_e
{
CONTACT_NONE = 0,
CONTACT_DOWN,
CONTACT_MOVE,
CONTACT_UP,
};
struct sam_sample_s
{
uint8_t id;
uint8_t contact;
bool valid;
uint16_t x;
uint16_t y;
#ifdef CONFIG_SAMA5_TSD_4WIRE
uint16_t p;
#endif
};
struct sam_tsd_s
{
uint8_t nwaiters;
uint8_t id;
uint8_t valid;
uint8_t crefs;
volatile bool penchange;
uint32_t threshx;
uint32_t threshy;
sem_t waitsem;
struct sam_adc_s *adc;
struct work_s work;
struct sam_sample_s sample;
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
struct wdog_s wdog;
#endif
struct g_tscaldata_s caldata;
bool scaled;
* scaled values (true) or not (false).
*/
uint32_t pending;
* 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_SAMA5_TSD_NPOLLWAITERS];
};
* Private Function Prototypes
****************************************************************************/
static void sam_tsd_notify(struct sam_tsd_s *priv);
static int sam_tsd_sample(struct sam_tsd_s *priv,
struct sam_sample_s *sample);
static int sam_tsd_waitsample(struct sam_tsd_s *priv,
struct sam_sample_s *sample);
static void sam_tsd_bottomhalf(void *arg);
static int sam_tsd_schedule(struct sam_tsd_s *priv);
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
static void sam_tsd_expiry(wdparm_t arg);
#endif
static int sam_tsd_open(struct file *filep);
static int sam_tsd_close(struct file *filep);
static ssize_t sam_tsd_read(struct file *filep, char *buffer, size_t len);
static int sam_tsd_ioctl(struct file *filep, int cmd, unsigned long arg);
static int sam_tsd_poll(struct file *filep, struct pollfd *fds, bool setup);
static void sam_tsd_startuptime(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_tracking(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_trigperiod(struct sam_tsd_s *priv, uint32_t period);
static void sam_tsd_debounce(struct sam_tsd_s *priv, uint32_t time);
static void sam_tsd_initialize(struct sam_tsd_s *priv);
static void sam_tsd_uninitialize(struct sam_tsd_s *priv);
* Private Data
****************************************************************************/
static const struct file_operations g_tsdops =
{
sam_tsd_open,
sam_tsd_close,
sam_tsd_read,
NULL,
NULL,
sam_tsd_ioctl,
NULL,
NULL,
sam_tsd_poll
};
static struct sam_tsd_s g_tsd =
{
.threshx = INVALID_THRESHOLD,
.threshy = INVALID_THRESHOLD,
.waitsem = SEM_INITIALIZER(0),
};
* Private Functions
****************************************************************************/
* Name: sam_tsd_notify
****************************************************************************/
static void sam_tsd_notify(struct sam_tsd_s *priv)
{
* 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.
*/
poll_notify(priv->fds, CONFIG_SAMA5_TSD_NPOLLWAITERS, POLLIN);
* that the read data is available.
*/
if (priv->nwaiters > 0)
{
* touchscreen is no longer available.
*/
nxsem_post(&priv->waitsem);
}
}
* Name: sam_tsd_sample
****************************************************************************/
static int sam_tsd_sample(struct sam_tsd_s *priv,
struct sam_sample_s *sample)
{
irqstate_t flags;
int ret = -EAGAIN;
* of semaphores from interrupt handlers, and (2) to prevent sampled data
* from changing until it has been reported.
*/
flags = enter_critical_section();
if (priv->penchange)
{
* sampled data.
*/
memcpy(sample, &priv->sample, sizeof(struct sam_sample_s));
if (sample->contact == CONTACT_UP)
{
* 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)
{
priv->sample.contact = CONTACT_MOVE;
}
priv->penchange = false;
ret = OK;
}
leave_critical_section(flags);
return ret;
}
* Name: sam_tsd_waitsample
****************************************************************************/
static int sam_tsd_waitsample(struct sam_tsd_s *priv,
struct sam_sample_s *sample)
{
irqstate_t flags;
int ret = 0;
* of semaphores from interrupt handlers, and (2) to prevent sampled data
* from changing until it has been reported.
*/
flags = enter_critical_section();
* the device structure. This may cause other tasks to become ready to
* run, but they cannot run yet because pre-emption is disabled.
*/
sam_adc_unlock(priv->adc);
* that is posted when new sample data is available.
*/
while (sam_tsd_sample(priv, sample) < 0)
{
iinfo("Waiting..\n");
priv->nwaiters++;
ret = nxsem_wait(&priv->waitsem);
priv->nwaiters--;
if (ret < 0)
{
ierr("ERROR: nxsem_wait: %d\n", ret);
goto errout;
}
}
iinfo("Sampled\n");
* 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.
*/
sam_adc_lock(priv->adc);
errout:
* could be a new sample. But no new threads will run because we still
* have pre-emption disabled.
*/
leave_critical_section(flags);
return ret;
}
* Name: sam_tsd_setaverage
*
* Description:
* The ADC hardware can filter the touchscreen samples by averaging. The
* function selects (or de-selects) that filtering.
*
* Input Parameters:
* priv - The touchscreen private data structure
* tsav - The new (shifted) value of the TSAV field of ADC TSMR register
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_setaverage(struct sam_tsd_s *priv, uint32_t tsav)
{
uint32_t regval;
uint32_t minfreq;
uint32_t tsfreq;
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
* frequency
*
* minfreq = 0: No filtering
* minfreq = 1: Averages 2 ADC conversions
* minfreq = 2: Averages 4 ADC conversions
* minfreq = 3: Averages 8 ADC conversions
*/
minfreq = (tsav >> ADC_TSMR_TSAV_SHIFT);
if (minfreq)
{
* Frequency. --> TSFREQ must be greater or equal to TSAV. <--
*/
tsfreq = (regval & ADC_TSMR_TSFREQ_MASK) >> ADC_TSMR_TSFREQ_SHIFT;
if (minfreq > tsfreq)
{
tsfreq = minfreq;
}
regval &= ~ADC_TSMR_TSFREQ_MASK;
regval |= ADC_TSMR_TSFREQ(minfreq);
}
regval &= ~ADC_TSMR_TSAV_MASK;
regval |= tsav;
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
}
* Name: sam_tsd_bottomhalf
*
* Description:
* This function executes on the worker thread. It is scheduled by
* sam_tsd_interrupt whenever any interesting, enabled TSD event occurs.
* All TSD interrupts are disabled when this function runs.
* sam_tsd_bottomhalf will re-enable TSD interrupts when it completes
* processing all pending TSD events.
*
* Input Parameters:
* arg - The touchscreen private data structure cast to (void *)
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_bottomhalf(void *arg)
{
struct sam_tsd_s *priv = (struct sam_tsd_s *)arg;
uint32_t pending;
uint32_t ier;
uint32_t regval;
uint32_t xraw;
uint32_t xscale;
uint32_t x;
uint32_t xdiff;
uint32_t yraw;
uint32_t yscale;
uint32_t y;
uint32_t ydiff;
#ifdef CONFIG_SAMA5_TSD_4WIRE
uint32_t z1;
uint32_t z2;
uint32_t pressr;
uint32_t p;
#endif
bool pendown;
DEBUGASSERT(priv != NULL);
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
sam_adc_lock(priv->adc);
pending = priv->pending;
* - Pen status is down OR
* - Pen down interrupt seen, but NOT if
* - Pen up interrrupt occurred as we need to deal with that
*/
pendown = ((((pending & ADC_SR_PENS) != 0) ||
((pending & ADC_INT_PEN) != 0)) &&
(pending & ADC_INT_NOPEN) == 0);
iinfo("pending: %08" PRIx32 " pendown: %d contact: %d\n",
pending, pendown, priv->sample.contact);
if (!pendown)
{
priv->threshx = INVALID_THRESHOLD;
priv->threshy = INVALID_THRESHOLD;
* want to hear anything from the touchscreen until the next touch.
*/
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
ier = ADC_INT_PEN;
#else
ier = ADC_TSD_PRESSINTS;
#endif
* up and already reported; CONTACT_UP == pen up, but not reported)
*/
if (priv->sample.contact == CONTACT_NONE ||
priv->sample.contact == CONTACT_UP)
{
iinfo("\t\t\t\t\tIgnored interrupt\n");
goto ignored;
}
* loss of contact condition. This will be changed to CONTACT_NONE
* after the loss of contact is sampled.
*/
priv->sample.contact = CONTACT_UP;
sam_tsd_setaverage(priv, ADC_TSMR_TSAV_NOFILTER);
sam_tsd_debounce(priv, BOARD_TSD_DEBOUNCE);
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_PEN;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
#endif
}
* 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)
{
* cannot handle this pen down event. We will have to discard it. That
* should be okay because we will set the timer to sample again
* a little later. NOTE that pen interrupts are not re-enabled in
* this case; we rely on the timer expiry to get us going again.
*/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_start(&priv->wdog, TSD_WDOG_DELAY,
sam_tsd_expiry, (wdparm_t)priv);
ier = 0;
#endif
iinfo("\t\t\t\t\tlast event not processed\n");
goto ignored;
}
else
{
* release events.
*/
ier = ADC_TSD_RELEASEINTS;
* re-enable interrupts and wait until it is.
*/
if ((pending & TSD_ALLREADY) != TSD_ALLREADY)
{
ier &= ~pending & TSD_ALLREADY;
* for TSD channels so periodic or continuous triggers are needed.
* We may be already using periodic triggers (for std adc ops).
*/
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
if ((regval & ADC_TSMR_TSAV_MASK) != 0)
{
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_PERIOD;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
}
#endif
iinfo("\t\t\t\t\tNot all data ready to read\n");
goto ignored;
}
iinfo("\t\t\t\t\tsampling data\n");
regval = sam_adc_getreg(priv->adc, SAM_ADC_XPOSR);
xraw = (regval & ADC_XPOSR_XPOS_MASK) >> ADC_XPOSR_XPOS_SHIFT;
xscale = (regval & ADC_XPOSR_XSCALE_MASK) >> ADC_XPOSR_XSCALE_SHIFT;
regval = sam_adc_getreg(priv->adc, SAM_ADC_YPOSR);
yraw = (regval & ADC_YPOSR_YPOS_MASK) >> ADC_YPOSR_YPOS_SHIFT;
yscale = (regval & ADC_YPOSR_YSCALE_MASK) >> ADC_YPOSR_YSCALE_SHIFT;
#ifdef CONFIG_SAMA5_TSD_4WIRE
* decide if we are going to use this measurement.
*/
pressr = sam_adc_getreg(priv->adc, SAM_ADC_PRESSR);
#endif
if (xraw == 0 || xraw > xscale || yraw == 0 || yraw > yscale)
{
iwarn("WARNING: Discarding: x %" PRId32 ":%" PRId32
" y %" PRId32 ":%" PRId32 "\n",
xraw, xscale,
yraw, yscale);
iinfo("\t\t\t\t\tBad reading\n");
goto ignored;
}
* value that the sample can attain. It should be close to 4095.
* Scaling:
*
* scaled = raw * 4095 / scale
* = ((raw << 12) - raw) / scale
*/
#ifdef CONFIG_SAMA5_TSD_SWAPXY
x = ((yraw << 12) - yraw) / yscale;
y = ((xraw << 12) - xraw) / xscale;
#else
x = ((xraw << 12) - xraw) / xscale;
y = ((yraw << 12) - yraw) / yscale;
#endif
* more stable. If the difference from the last sample is small,
* then ignore the event. REVISIT: Should a large change in
* pressure also generate a event?
*/
xdiff = x > priv->threshx ? (x - priv->threshx) : (priv->threshx - x);
ydiff = y > priv->threshy ? (y - priv->threshy) : (priv->threshy - y);
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_start(&priv->wdog, TSD_WDOG_DELAY,
sam_tsd_expiry, (wdparm_t)priv);
#endif
* measurement and (2) there is no significant difference from
* the last measurement.
*/
if (priv->sample.contact == CONTACT_MOVE &&
xdiff < CONFIG_SAMA5_TSD_THRESHX &&
ydiff < CONFIG_SAMA5_TSD_THRESHY)
{
* anything.
*/
iinfo("\t\t\t\t\tNo change\n");
goto ignored;
}
priv->threshx = x;
priv->threshy = y;
priv->sample.x = MIN(x, UINT16_MAX);
priv->sample.y = MIN(y, UINT16_MAX);
#ifdef CONFIG_SAMA5_TSD_4WIRE
*
* The method to measure the pressure (Rp) applied to the
* touchscreen is based on the known resistance of the X-Panel
* resistance (Rxp). Three conversions (Xpos, Z1, Z2) are
* necessary to determine the value of Rp (Zaxis resistance).
*
* Rp = Rxp * (Xpos / 1024) * [(Z2 / Z1) - 1]
*
*/
z2 = (pressr & ADC_PRESSR_Z2_MASK) >> ADC_PRESSR_Z2_SHIFT;
z1 = (pressr & ADC_PRESSR_Z1_MASK) >> ADC_PRESSR_Z1_SHIFT;
if (z1 != 0)
{
p = CONFIG_SAMA_TSD_RXP * xraw * (z2 - z1) / (z1 * 1024);
}
priv->sample.p = UINT16_MAX - MIN(p, UINT16_MAX);
#endif
priv->sample.valid = true;
* 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)
{
priv->sample.contact = CONTACT_DOWN;
sam_tsd_setaverage(priv, BOARD_TSD_PENDOWN_TSAV);
sam_tsd_debounce(priv, 300);
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_PERIOD;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
#endif
}
}
priv->sample.id = priv->id;
priv->penchange = true;
sam_tsd_notify(priv);
ignored:
sam_adc_putreg(priv->adc, SAM_ADC_IER, ier);
sam_adc_unlock(priv->adc);
}
* Name: sam_tsd_schedule
****************************************************************************/
static int sam_tsd_schedule(struct sam_tsd_s *priv)
{
int ret;
* while the pen remains down.
*/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_cancel(&priv->wdog);
#endif
* re-enabled after the worker thread executes.
*/
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
* 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, sam_tsd_bottomhalf, priv, 0);
if (ret != 0)
{
ierr("ERROR: Failed to queue work: %d\n", ret);
}
return OK;
}
* Name: sam_tsd_expiry
*
* Description:
* While the pen is pressed, pen position is periodically polled via a
* watchdog timer. This function handles that timer expiration.
*
****************************************************************************/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
static void sam_tsd_expiry(wdparm_t arg)
{
struct sam_tsd_s *priv = (struct sam_tsd_s *)arg;
sam_tsd_schedule(priv);
}
#endif
* Name: sam_tsd_open
****************************************************************************/
static int sam_tsd_open(struct file *filep)
{
struct inode *inode = filep->f_inode;
struct sam_tsd_s *priv = inode->i_private;
uint8_t tmp;
int ret;
iinfo("crefs: %d\n", priv->crefs);
sam_adc_lock(priv->adc);
* time that the driver has been opened for this device, then initialize
* the device.
*/
tmp = priv->crefs + 1;
if (tmp == 0)
{
ret = -EAGAIN;
}
else
{
priv->crefs = tmp;
if (tmp == 1)
{
sam_tsd_initialize(priv);
}
ret = OK;
}
sam_adc_unlock(priv->adc);
return ret;
}
* Name: sam_tsd_close
****************************************************************************/
static int sam_tsd_close(struct file *filep)
{
struct inode *inode = filep->f_inode;
struct sam_tsd_s *priv = inode->i_private;
iinfo("crefs: %d\n", priv->crefs);
sam_adc_lock(priv->adc);
DEBUGASSERT(priv->crefs > 0);
priv->crefs--;
* TSD now.
*/
if (priv->crefs == 0)
{
sam_tsd_uninitialize(priv);
}
sam_adc_unlock(priv->adc);
return OK;
}
* Name: sam_tsd_read
****************************************************************************/
static ssize_t sam_tsd_read(struct file *filep, char *buffer, size_t len)
{
struct inode *inode;
struct sam_tsd_s *priv;
struct touch_sample_s *report;
struct sam_sample_s sample;
int regval;
int ret;
iinfo("buffer:%p len:%d\n", buffer, len);
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
* the touch data.
*/
if (len < SIZEOF_TOUCH_SAMPLE_S(1))
{
* handle smaller reads... but why?
*/
ierr("ERROR: Unsupported read size: %d\n", len);
return -ENOSYS;
}
sam_adc_lock(priv->adc);
ret = sam_tsd_sample(priv, &sample);
if (ret < 0)
{
* receive sample data. If the user has specified the O_NONBLOCK
* option, then just return an error.
*/
iinfo("Sample data is not available\n");
if (filep->f_oflags & O_NONBLOCK)
{
ret = -EAGAIN;
goto errout;
}
else
{
* we cause the system to hang because tsd_wait_sample will enter
* a critical section until an adc sample is available - which may
* be a very long time if the pen detect ADC trigger is in use.
*/
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ADC_TRGR_TRGMOD_MASK;
DEBUGASSERT(regval != ADC_TRGR_TRGMOD_PEN);
}
ret = sam_tsd_waitsample(priv, &sample);
if (ret < 0)
{
ierr("ERROR: sam_tsd_waitsample: %d\n", ret);
goto errout;
}
}
* to the caller.
*/
report = (struct touch_sample_s *)buffer;
memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(1));
report->npoints = 1;
report->point[0].id = sample.id;
if (priv->scaled)
{
report->point[0].x = SCALE_TS(itob16(sample.x), priv->caldata.offset_x,
priv->caldata.slope_x);
report->point[0].y = SCALE_TS(itob16(sample.y), priv->caldata.offset_y,
priv->caldata.slope_y);
}
else
{
report->point[0].x = sample.x;
report->point[0].y = sample.y;
}
#ifdef CONFIG_SAMA5_TSD_4WIRE
report->point[0].pressure = sample.p;
#endif
if (sample.contact == CONTACT_UP)
{
* to know because the release will be sent to the window based on
* its last positional data.
*/
if (sample.valid)
{
report->point[0].flags = TSD_PENUP_VALID;
}
else
{
report->point[0].flags = TSD_PENUP_INVALID;
}
}
else if (sample.contact == CONTACT_DOWN)
{
report->point[0].flags = TSD_PENDOWN;
}
else
{
report->point[0].flags = TSD_PENMOVE;
}
iinfo(" id: %d\n", report->point[0].id);
iinfo(" flags: %02x\n", report->point[0].flags);
iinfo(" x: %d\n", report->point[0].x);
iinfo(" y: %d\n", report->point[0].y);
ret = SIZEOF_TOUCH_SAMPLE_S(1);
errout:
sam_adc_unlock(priv->adc);
iinfo("Returning: %d\n", ret);
return (ssize_t)ret;
}
* Name: sam_tsd_ioctl
****************************************************************************/
static int sam_tsd_ioctl(struct file *filep, int cmd, unsigned long arg)
{
struct inode *inode;
struct sam_tsd_s *priv;
int ret = OK;
int regval;
iinfo("cmd: %d arg: %ld\n", cmd, arg);
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
sam_adc_lock(priv->adc);
switch (cmd)
{
case TSIOC_DOACALIB:
{
regval = sam_adc_getreg(priv->adc, SAM_ADC_ACR);
regval = ADC_CR_TSCALIB | ADC_CR_START;
sam_adc_putreg(priv->adc, SAM_ADC_CR, regval);
}
break;
case TSIOC_CALDATA:
{
struct g_tscaldata_s *ptr =
(struct g_tscaldata_s *)((uintptr_t)arg);
DEBUGASSERT(ptr != NULL);
priv->caldata = *ptr;
}
break;
case TSIOC_USESCALED:
{
priv->scaled = (bool)(arg);
}
break;
default:
ret = -ENOTTY;
break;
}
sam_adc_unlock(priv->adc);
return ret;
}
* Name: sam_tsd_poll
****************************************************************************/
static int sam_tsd_poll(struct file *filep, struct pollfd *fds, bool setup)
{
struct inode *inode;
struct sam_tsd_s *priv;
int ret = OK;
int i;
iinfo("setup: %d\n", (int)setup);
DEBUGASSERT(fds);
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
priv = inode->i_private;
sam_adc_lock(priv->adc);
if (setup)
{
if ((fds->events & POLLIN) == 0)
{
ret = -EDEADLK;
goto errout;
}
* slot for the poll structure reference
*/
for (i = 0; i < CONFIG_SAMA5_TSD_NPOLLWAITERS; i++)
{
if (!priv->fds[i])
{
priv->fds[i] = fds;
fds->priv = &priv->fds[i];
break;
}
}
if (i >= CONFIG_SAMA5_TSD_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto errout;
}
if (priv->penchange)
{
poll_notify(&fds, 1, POLLIN);
}
}
else if (fds->priv)
{
struct pollfd **slot = (struct pollfd **)fds->priv;
DEBUGASSERT(slot != NULL);
*slot = NULL;
fds->priv = NULL;
}
errout:
sam_adc_unlock(priv->adc);
return ret;
}
* Initialization and Configuration
****************************************************************************/
* Name: sam_tsd_startuptime
*
* Description:
* Set the STARTUP field of the ADC MR register.
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
* time - The new startup time in nanoseconds
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_startuptime(struct sam_tsd_s *priv, uint32_t time)
{
uint32_t startup;
uint32_t regval;
*
* STARTUP = (time x ADCCLK) / (1000000) - 1
*
* Division multiplied by 10 for higher precision.
*/
startup = (time * BOARD_ADCCLK_FREQUENCY) / 100000;
if (startup % 10)
{
* basically a 'ceil' operation.
*/
startup /= 10;
}
else
{
startup /= 10;
if (startup)
{
startup--;
}
}
regval = sam_adc_getreg(priv->adc, SAM_ADC_MR);
regval &= ~ADC_MR_STARTUP_MASK;
if (startup > 896)
{
regval |= ADC_MR_STARTUP_960;
}
else if (startup > 832)
{
regval |= ADC_MR_STARTUP_896;
}
else if (startup > 768)
{
regval |= ADC_MR_STARTUP_832;
}
else if (startup > 704)
{
regval |= ADC_MR_STARTUP_768;
}
else if (startup > 640)
{
regval |= ADC_MR_STARTUP_704;
}
else if (startup > 576)
{
regval |= ADC_MR_STARTUP_640;
}
else if (startup > 512)
{
regval |= ADC_MR_STARTUP_576;
}
else if (startup > 112)
{
regval |= ADC_MR_STARTUP_512;
}
else if (startup > 96)
{
regval |= ADC_MR_STARTUP_112;
}
else if (startup > 80)
{
regval |= ADC_MR_STARTUP_96;
}
else if (startup > 64)
{
regval |= ADC_MR_STARTUP_80;
}
else if (startup > 24)
{
regval |= ADC_MR_STARTUP_64;
}
else if (startup > 16)
{
regval |= ADC_MR_STARTUP_24;
}
else if (startup > 8)
{
regval |= ADC_MR_STARTUP_16;
}
else if (startup > 0)
{
regval |= ADC_MR_STARTUP_8;
}
else
{
regval |= ADC_MR_STARTUP_0;
}
sam_adc_putreg(priv->adc, SAM_ADC_MR, regval);
}
* Name: sam_tsd_tracking
*
* Description:
* Set the TRACKTIM field of the ADC MR register.
*
* TrackingTime = (TRACKTIM + 1) * ADCClock periods.
* TRACKTIM = (TrackingTime * ADCCLK) / (1000000000) - 1
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
* time - The new tracking time in nanoseconds
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_tracking(struct sam_tsd_s *priv, uint32_t time)
{
uint32_t tracktim;
uint32_t regval;
#if defined(ATSAMA5D4)
*
* TRACKTIM = (TrackingTime * ADCCLK) / (1000000000) - 1
*
* Since 1 billion is close to the maximum value for an integer, we first
* divide ADCCLK by 1000 to avoid an overflow
*/
tracktim = (time * (BOARD_ADCCLK_FREQUENCY / 1000)) / 100000;
if (tracktim % 10)
{
* basically a 'ceil' operation.
*/
tracktim /= 10;
}
else
{
tracktim /= 10;
if (tracktim)
{
tracktim--;
}
}
#elif defined(ATSAMA5D3)
tracktim = 0;
#else
tracktim = MAX(time, 15);
#endif
regval = sam_adc_getreg(priv->adc, SAM_ADC_MR);
regval &= ~ADC_MR_TRACKTIM_MASK;
regval |= ADC_MR_TRACKTIM(tracktim);
sam_adc_putreg(priv->adc, SAM_ADC_MR, regval);
}
* Name: sam_tsd_trigperiod
*
* Description:
* Set the TGPER field of the TRGR register in order to define a periodic
* trigger perioc.
*
* Trigger Period = (TRGPER+1) / ADCCLK
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
* time - The new trigger period in useconds
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_trigperiod(struct sam_tsd_s *priv, uint32_t period)
{
uint32_t trigper;
uint32_t regval;
uint32_t div;
* appropriate here because times are specified in decimal with lots of
* zeroes.
*/
div = 100000;
while (period >= 10 && div >= 10)
{
period /= 10;
div /= 10;
}
*
* Trigger Period = (TRGPER+1) / ADCCLK
*/
trigper = (period * BOARD_ADCCLK_FREQUENCY) / div;
if ((trigper % 10) != 0)
{
* basically a 'ceil' operation.
*/
trigper /= 10;
}
else
{
trigper /= 10;
if (trigper > 0)
{
trigper--;
}
}
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGPER_MASK;
regval |= ADC_TRGR_TRGPER(trigper);
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
}
* Name: sam_tsd_debounce
*
* Description:
* Set the Pen Detect Debouncing Period (PENBC) in the touchscreen mode
* register.
*
* Debouncing period = 2 ** PENDBC ADCClock periods.
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
* time - The new debounce time in nanoseconds
*
* Returned Value:
* None
*
****************************************************************************/
static void sam_tsd_debounce(struct sam_tsd_s *priv, uint32_t time)
{
uint32_t candidate;
uint32_t target;
uint32_t regval;
uint32_t pendbc;
uint32_t div;
uint32_t clk;
DEBUGASSERT(time > 0);
* but appropriate here because times are specified in decimal with lots of
* zeroes.
*/
div = 1000000000;
while (div > 1 && (time % 10) == 0)
{
time /= 10;
div /= 10;
}
clk = BOARD_ADCCLK_FREQUENCY;
while (div > 1 && (clk % 10) == 0)
{
clk /= 10;
div /= 10;
}
target = time * clk / div;
candidate = 1;
pendbc = 0;
while (candidate < target)
{
pendbc++;
candidate *= 2;
}
DEBUGASSERT(pendbc > 0);
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
regval &= ~ADC_TSMR_PENDBC_MASK;
regval |= ADC_TSMR_PENDBC(pendbc);
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
}
* Name: sam_tsd_initialize
*
* Description:
* Initialize the touchscreen for normal operation. This function is
* called from sam_tsd_open() the first time that the driver is opened.
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
*
* Returned Value:
* Zero is returned on success. Otherwise, a negated errno value is
* returned to indicate the nature of the failure.
*
****************************************************************************/
static void sam_tsd_initialize(struct sam_tsd_s *priv)
{
uint32_t regval;
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_NOTRIG;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
#endif
sam_tsd_startuptime(priv, BOARD_TSD_STARTUP);
sam_tsd_tracking(priv, BOARD_TSD_TRACKTIM);
* been initialised. It's the only option allowed and that works, unless
* continuous mode is set of course.
*/
#ifdef CONFIG_SAMA5_ADC_TRIGGER_PERIOD
regval = sam_adc_getreg(priv, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_PERIOD;
sam_adc_putreg(priv, SAM_ADC_TRGR, regval);
sam_tsd_trigperiod(priv, CONFIG_SAMA5_ADC_TRIGGER_PERIOD);
sam_tsd_trigperiod(priv, CONFIG_SAMA5_ADC_TRIGGER_PERIOD);
#else
sam_tsd_trigperiod(priv, 20000);
#endif
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
regval &= ~ADC_TSMR_TSMODE_MASK;
#if defined(CONFIG_SAMA5_TSD_5WIRE)
regval |= ADC_TSMR_TSMODE_5WIRE;
#elif defined(CONFIG_SAMA5_TSD_4WIRENPM)
regval |= ADC_TSMR_TSMODE_4WIRENPM;
#else
regval |= ADC_TSMR_TSMODE_4WIRE;
#endif
regval &= ~ADC_TSMR_TSSCTIM_MASK;
regval |= ADC_TSMR_TSSCTIM(BOARD_TSSCTIM);
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
sam_tsd_setaverage(priv, ADC_TSMR_TSAV_NOFILTER);
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
sam_adc_getreg(priv->adc, SAM_ADC_ISR);
sam_adc_getreg(priv->adc, SAM_ADC_XPOSR);
sam_adc_getreg(priv->adc, SAM_ADC_YPOSR);
#ifdef CONFIG_SAMA5_TSD_4WIRE
sam_adc_getreg(priv->adc, SAM_ADC_PRESSR);
#endif
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
regval |= ADC_TSMR_PENDET;
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
sam_tsd_debounce(priv, BOARD_TSD_DEBOUNCE);
regval = sam_adc_getreg(priv->adc, SAM_ADC_ACR);
regval &= ~ADC_ACR_PENDETSENS_MASK;
regval |= ADC_ACR_PENDETSENS(BOARD_TSD_PENDETSENS);
#if defined(ATSAMA5D2)
regval &= ~ADC_ACR_IBCTL_MASK;
regval |= ADC_ACR_IBCTL(BOARD_TSD_IBCTL);
#endif
sam_adc_putreg(priv->adc, SAM_ADC_ACR, regval);
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_PEN;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
#endif
sam_adc_putreg(priv->adc, SAM_ADC_IER, ADC_INT_PEN);
#ifdef CONFIG_SAMA5_TSD_AUTOCALIB
regval = ADC_CR_TSCALIB | ADC_CR_START;
sam_adc_putreg(priv->adc, SAM_ADC_CR, regval);
#endif
regval = sam_adc_getreg(priv->adc, SAM_ADC_ISR);
up_enable_irq(SAM_IRQ_ADC);
}
* Name: sam_tsd_uninitialize
*
* Description:
* Uninitialize the touchscreen. This function is called from
* sam_tsd_close() when the final driver instance is closed.
*
* Input Parameters:
* priv - A reference to the touchscreen device structure
*
* Returned Value:
* Zero is returned on success. Otherwise, a negated errno value is
* returned to indicate the nature of the failure.
*
****************************************************************************/
static void sam_tsd_uninitialize(struct sam_tsd_s *priv)
{
uint32_t regval;
* while the pen remains down.
*/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_cancel(&priv->wdog);
#endif
* re-enabled after the worker thread executes.
*/
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
regval = sam_adc_getreg(priv->adc, SAM_ADC_TRGR);
regval &= ~ADC_TRGR_TRGMOD_MASK;
regval |= ADC_TRGR_TRGMOD_NOTRIG;
sam_adc_putreg(priv->adc, SAM_ADC_TRGR, regval);
#endif
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
regval &= ~ADC_TSMR_TSMODE_MASK;
regval |= ADC_TSMR_TSMODE_NONE;
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
priv->sample.contact = CONTACT_NONE;
priv->sample.valid = false;
}
* Public Functions
****************************************************************************/
* Name: sam_tsd_register
*
* Description:
* Configure the SAMA5 touchscreen. This will register the driver as
* /dev/inputN where N is the minor device number
*
* Input Parameters:
* adc - An opaque reference to the ADC device structure
* 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 sam_tsd_register(struct sam_adc_s *adc, int minor)
{
struct sam_tsd_s *priv = &g_tsd;
char devname[DEV_NAMELEN];
int ret;
iinfo("minor: %d\n", minor);
DEBUGASSERT(adc && minor >= 0 && minor < 100);
priv->adc = adc;
snprintf(devname, sizeof(devname), DEV_FORMAT, minor);
iinfo("Registering %s\n", devname);
ret = register_driver(devname, &g_tsdops, 0666, priv);
if (ret < 0)
{
ierr("ERROR: register_driver() failed: %d\n", ret);
return ret;
}
* touchscreen driver is opened for the first time.
*/
return OK;
}
* Name: sam_tsd_interrupt
*
* Description:
* Handles ADC interrupts associated with touchscreen channels
*
* Input Parameters:
* pending - Current set of pending interrupts being handled
*
* Returned Value:
* None
*
****************************************************************************/
void sam_tsd_interrupt(uint32_t pending)
{
struct sam_tsd_s *priv = &g_tsd;
int ret;
if ((pending & ADC_TSD_ALLINTS) != 0)
{
* worker thread.
*/
priv->pending = pending;
ret = sam_tsd_schedule(priv);
if (ret < 0)
{
ierr("ERROR: sam_tsd_schedule failed: %d\n", ret);
}
}
}
#endif