blob: e6f31cbc6490e73cc90c4f1261ae7349e91eeef5 [file] [log] [blame]
/****************************************************************************
* 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.
*
****************************************************************************/
/* References:
*
* 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
****************************************************************************/
/* 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 24
/* Poll the pen position while the pen is down at this rate (50MS): */
#define TSD_WDOG_DELAY MSEC2TICK(50)
/* This is a value for the threshold that guarantees a big difference on the
* first pendown (but can't overflow).
*/
#define INVALID_THRESHOLD 0x1000
/* Data read bit definitions */
#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
/* Pen sample state bit sets */
#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
/* The driver has always used 8 sample averages for the default filtering,
* 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
****************************************************************************/
/* This describes the state of one contact */
enum sam_contact_e
{
CONTACT_NONE = 0, /* No contact */
CONTACT_DOWN, /* First contact */
CONTACT_MOVE, /* Same contact, possibly diff. position */
CONTACT_UP, /* Contact lost */
};
/* This structure describes the results of one touchscreen sample */
struct sam_sample_s
{
uint8_t id; /* Sampled touch point ID */
uint8_t contact; /* Contact state (see enum sam_contact_e) */
bool valid; /* True: x,y,p contain valid, sampled data */
uint16_t x; /* Measured X position */
uint16_t y; /* Measured Y position */
#ifdef CONFIG_SAMA5_TSD_4WIRE
uint16_t p; /* Measured pressure */
#endif
};
/* This structure describes the state of one touchscreen driver instance */
struct sam_tsd_s
{
uint8_t nwaiters; /* Num threads waiting for TSD data */
uint8_t id; /* Current touch point ID */
uint8_t valid; /* Data ready bit set */
uint8_t crefs; /* Number of times device opened */
volatile bool penchange; /* An unreported event is buffered */
uint32_t threshx; /* Thresholding X value */
uint32_t threshy; /* Thresholding Y value */
sem_t waitsem; /* Used to wait for data available */
struct sam_adc_s *adc; /* ADC device handle */
struct work_s work; /* Supports int. handling "bottom half" */
struct sam_sample_s sample; /* Last sampled touch point data */
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
struct wdog_s wdog; /* Poll position while the pen is down */
#endif
struct g_tscaldata_s caldata; /* Touchscreen Calibration Data */
bool scaled; /* Character dreiver read should return
* scaled values (true) or not (false).
*/
uint32_t pending; /* saved ISR status from the ADC driver */
/* 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_SAMA5_TSD_NPOLLWAITERS];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Interrupt bottom half logic and data sampling */
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
/* Character driver methods */
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);
/* Initialization and configuration */
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
****************************************************************************/
/* This the vtable that supports the character driver interface */
static const struct file_operations g_tsdops =
{
sam_tsd_open, /* open */
sam_tsd_close, /* close */
sam_tsd_read, /* read */
NULL, /* write */
NULL, /* seek */
sam_tsd_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
sam_tsd_poll /* poll */
};
/* The driver state structure is pre-allocated. */
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)
{
/* If there are threads waiting on poll() for touchscreen 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.
*/
poll_notify(priv->fds, CONFIG_SAMA5_TSD_NPOLLWAITERS, POLLIN);
/* 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
* 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;
/* Interrupts must 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 touchscreen 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 sam_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: sam_tsd_waitsample
****************************************************************************/
static int sam_tsd_waitsample(struct sam_tsd_s *priv,
struct sam_sample_s *sample)
{
irqstate_t flags;
int ret = 0;
/* Interrupts must 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();
/* 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.
*/
sam_adc_unlock(priv->adc);
/* Try to get the a sample... if we cannot, then wait on the semaphore
* that is posted when new sample data is available.
*/
while (sam_tsd_sample(priv, sample) < 0)
{
/* Wait for a sample data */
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");
/* 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.
*/
sam_adc_lock(priv->adc);
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);
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;
/* Get the current value of the TSMR register */
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
/* Get the unshifted TSAVE value and compare it to the touchscreen
* 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)
{
/* TSFREQ: Defines the Touchscreen Frequency compared to the Trigger
* Frequency. --> TSFREQ must be greater or equal to TSAV. <--
*/
tsfreq = (regval & ADC_TSMR_TSFREQ_MASK) >> ADC_TSMR_TSFREQ_SHIFT;
if (minfreq > tsfreq)
{
/* Set TSFREQ = TSAV */
tsfreq = minfreq;
}
regval &= ~ADC_TSMR_TSFREQ_MASK;
regval |= ADC_TSMR_TSFREQ(minfreq);
}
/* Save the new filter value */
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);
/* Get exclusive access to the driver data structure */
sam_adc_lock(priv->adc);
pending = priv->pending; /* ISR status passed to us from the ADC driver */
/* Check the pen state. Down if:
* - 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);
/* Handle the change from pen down to pen up */
iinfo("pending: %08" PRIx32 " pendown: %d contact: %d\n",
pending, pendown, priv->sample.contact);
if (!pendown)
{
/* The pen is up.. reset thresholding variables. */
priv->threshx = INVALID_THRESHOLD;
priv->threshy = INVALID_THRESHOLD;
/* We will enable only the ADC_INT_PEN interrupt on exit. We don't
* 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
/* Ignore the interrupt if the pen was already up (CONTACT_NONE == pen
* 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;
}
/* 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;
/* Stop periodic trigger & enable pen */
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
}
/* 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)
{
/* If we have not yet processed the last pen up event, then we
* 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
{
/* The pen is down and the driver accepted the last sample values. */
/* While the pen is down we want interrupts on all data ready and pen
* release events.
*/
ier = ADC_TSD_RELEASEINTS;
/* Check if all of the data that we need is available. If not, just
* re-enable interrupts and wait until it is.
*/
if ((pending & TSD_ALLREADY) != TSD_ALLREADY)
{
/* But don't enable interrupts for the data that we already have */
ier &= ~pending & TSD_ALLREADY;
/* datasheet says that if TSAV != 0 there may not be interrupts
* 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);
/* Configure for periodic trigger */
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;
}
/* Sample positional values. Get raw X and Y position data */
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
/* Read the PRESSR register now, but don't do anything until we
* decide if we are going to use this measurement.
*/
pressr = sam_adc_getreg(priv->adc, SAM_ADC_PRESSR);
#endif
/* Discard any bad readings. This check may not be necessary. */
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;
}
/* Scale the X/Y measurements. The scale value is the maximum
* 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
/* Perform a thresholding operation so that the results will be
* 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);
/* Continue to sample the position while the pen is down */
#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
/* Check the thresholds. Bail if (1) this is not the first
* 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)
{
/* Little or no change in either direction ... don't report
* anything.
*/
iinfo("\t\t\t\t\tNo change\n");
goto ignored;
}
/* When we see a big difference, snap to the new x/y thresholds */
priv->threshx = x;
priv->threshy = y;
/* Update the x/y position in the sample data */
priv->sample.x = MIN(x, UINT16_MAX);
priv->sample.y = MIN(y, UINT16_MAX);
#ifdef CONFIG_SAMA5_TSD_4WIRE
/* Scale the pressure and update the pressure in the sample data.
*
* 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
/* The X/Y positional data is now valid */
priv->sample.valid = true;
/* 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. Handle transitions from pen UP to pen DOWN */
priv->sample.contact = CONTACT_DOWN;
sam_tsd_setaverage(priv, BOARD_TSD_PENDOWN_TSAV);
sam_tsd_debounce(priv, 300); /* 300ns */
/* Configure for periodic trigger */
#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
}
}
/* Indicate the availability of new sample data for this ID */
priv->sample.id = priv->id;
priv->penchange = true;
/* Notify any waiters that new touchscreen data is available */
sam_tsd_notify(priv);
/* Exit, re-enabling touchscreen interrupts */
ignored:
/* Re-enable touchscreen interrupts as appropriate. */
sam_adc_putreg(priv->adc, SAM_ADC_IER, ier);
/* Release our lock on the state structure */
sam_adc_unlock(priv->adc);
}
/****************************************************************************
* Name: sam_tsd_schedule
****************************************************************************/
static int sam_tsd_schedule(struct sam_tsd_s *priv)
{
int ret;
/* Disable the watchdog timer. It will be re-enabled in the worker thread
* while the pen remains down.
*/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_cancel(&priv->wdog);
#endif
/* Disable further touchscreen interrupts. Touchscreen interrupts will be
* re-enabled after the worker thread executes.
*/
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
/* Transfer processing to the worker thread. Since touchscreen ADC
* 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;
/* Schedule touchscreen work */
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);
/* Get exclusive access to the device structures */
sam_adc_lock(priv->adc);
/* Increment the count of references to the device. If this the first
* time that the driver has been opened for this device, then initialize
* the device.
*/
tmp = priv->crefs + 1;
if (tmp == 0)
{
/* More than 255 opens; uint8_t overflows to zero */
ret = -EAGAIN;
}
else
{
/* Save the new open count */
priv->crefs = tmp;
/* Initialize the touchscreen when it is first opened */
if (tmp == 1)
{
sam_tsd_initialize(priv);
}
/* Successfully opened */
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);
/* Get exclusive access to the ADC device */
sam_adc_lock(priv->adc);
/* Decrement the references to the driver */
DEBUGASSERT(priv->crefs > 0);
priv->crefs--;
/* If this was the last reference to the driver, then un-initialize the
* 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;
/* 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?
*/
ierr("ERROR: Unsupported read size: %d\n", len);
return -ENOSYS;
}
/* Get exclusive access to the driver data structure */
sam_adc_lock(priv->adc);
/* Try to read sample data. */
ret = sam_tsd_sample(priv, &sample);
if (ret < 0)
{
/* Sample data is not available now. We would have to wait to get
* 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
{
/* If we have opened in blocking mode, there is a big risk that
* 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);
}
/* Wait for sample data */
ret = sam_tsd_waitsample(priv, &sample);
if (ret < 0)
{
/* We might have been awakened by a signal */
ierr("ERROR: sam_tsd_waitsample: %d\n", ret);
goto errout;
}
}
/* In any event, we now have sampled touchscreen data that we can report
* 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
/* 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 = TSD_PENUP_VALID;
}
else
{
report->point[0].flags = TSD_PENUP_INVALID;
}
}
else if (sample.contact == CONTACT_DOWN)
{
/* First contact */
report->point[0].flags = TSD_PENDOWN;
}
else /* if (sample->contact == CONTACT_MOVE) */
{
/* Movement of the same contact */
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;
/* Get exclusive access to the driver data structure */
sam_adc_lock(priv->adc);
/* Process the IOCTL by command */
switch (cmd)
{
case TSIOC_DOACALIB:
{
/* Initiate auto-calibration sequence */
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:
{
/* Receive calibration data for the touchscreen */
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;
/* Get exclusive access to the ADC hardware */
sam_adc_lock(priv->adc);
/* Are we setting up the poll? Or tearing it down? */
if (setup)
{
/* Ignore waits that do not include POLLIN */
if ((fds->events & POLLIN) == 0)
{
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_SAMA5_TSD_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_SAMA5_TSD_NPOLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto errout;
}
/* Should we immediately notify on any of the requested events? */
if (priv->penchange)
{
poll_notify(&fds, 1, POLLIN);
}
}
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:
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;
/* Formula for STARTUP is:
*
* STARTUP = (time x ADCCLK) / (1000000) - 1
*
* Division multiplied by 10 for higher precision.
*/
startup = (time * BOARD_ADCCLK_FREQUENCY) / 100000;
if (startup % 10)
{
/* Handle partial values by not decrementing startup. This is
* basically a 'ceil' operation.
*/
startup /= 10;
}
else
{
/* The final value needs to be decrement by one */
startup /= 10;
if (startup)
{
startup--;
}
}
/* Then set the STARTUP value in the ADC MR register. */
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)
/* Formula for SHTIM is:
*
* 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)
{
/* Handle partial values by not decrementing tracktim. This is
* basically a 'ceil' operation.
*/
tracktim /= 10;
}
else
{
/* The final value needs to be decrement by one */
tracktim /= 10;
if (tracktim)
{
tracktim--;
}
}
#elif defined(ATSAMA5D3)
tracktim = 0;
#else /* ATSAMA5D2*/
tracktim = MAX(time, 15);
#endif
/* Set the neew TRACKTIM field value int he ADC MR register */
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;
/* Divide trigger period avoid overflows. Division by ten is awkard, but
* appropriate here because times are specified in decimal with lots of
* zeroes.
*/
div = 100000;
while (period >= 10 && div >= 10)
{
period /= 10;
div /= 10;
}
/* Calculate and adjust the scaled trigger period:
*
* Trigger Period = (TRGPER+1) / ADCCLK
*/
trigper = (period * BOARD_ADCCLK_FREQUENCY) / div;
if ((trigper % 10) != 0)
{
/* Handle partial values by not decrementing trigper. This is
* basically a 'ceil' operation.
*/
trigper /= 10;
}
else
{
/* The final value needs to be decrement by one */
trigper /= 10;
if (trigper > 0)
{
trigper--;
}
}
/* Set the calculated trigger period in the TRGR register */
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;
/* Make sure that a valid debounce time was provided */
DEBUGASSERT(time > 0);
/* Divide time and ADCCLK to avoid overflows. Division by ten is awkard,
* 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;
}
/* Compute PENDBC */
target = time * clk / div;
candidate = 1;
pendbc = 0;
while (candidate < target)
{
pendbc++;
candidate *= 2;
}
DEBUGASSERT(pendbc > 0);
/* Update the TSMR with the new PENBC value */
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
/* Disable touch trigger */
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
/* Setup timing */
sam_tsd_startuptime(priv, BOARD_TSD_STARTUP);
sam_tsd_tracking(priv, BOARD_TSD_TRACKTIM);
/* set trigger mode to be periodic in case ADC not already
* 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);
/* Make sure the configured trigger period is used */
sam_tsd_trigperiod(priv, CONFIG_SAMA5_ADC_TRIGGER_PERIOD);
#else
sam_tsd_trigperiod(priv, 20000); /* 20ms */
#endif
/* Setup the touchscreen mode */
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 /* if defined(CONFIG_SAMA5_TSD_4WIRE) */
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);
/* Disable averaging */
sam_tsd_setaverage(priv, ADC_TSMR_TSAV_NOFILTER);
/* Disable all TSD-related interrupts */
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
/* Clear any pending TSD interrupts */
sam_adc_getreg(priv->adc, SAM_ADC_ISR);
/* Read and discard any samples */
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
/* Enable pen contact detection */
regval = sam_adc_getreg(priv->adc, SAM_ADC_TSMR);
regval |= ADC_TSMR_PENDET;
sam_adc_putreg(priv->adc, SAM_ADC_TSMR, regval);
/* Set up pen debounce time */
sam_tsd_debounce(priv, BOARD_TSD_DEBOUNCE);
/* Configure pen sensitivity */
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 /* we assume periodic otherwise */
/* Configure pen interrupt generation */
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
/* Perform a ts calibration */
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;
/* Disable the watchdog timer. It will be re-enabled in the worker thread
* while the pen remains down.
*/
#if !defined(CONFIG_SAMA5_ADC_PERIODIC_TRIG) && \
!defined(CONFIG_SAMA5_ADC_CONTINUOUS_TRIG)
wd_cancel(&priv->wdog);
#endif
/* Disable further touchscreen interrupts. Touchscreen interrupts will be
* re-enabled after the worker thread executes.
*/
sam_adc_putreg(priv->adc, SAM_ADC_IDR, ADC_TSD_ALLINTS);
#ifdef SAMA5_TSD_TRIG_CHANGE_ALLOWED
/* Disable touch trigger */
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
/* Disable the touchscreen mode */
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);
/* No touch and no valid sample data */
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);
/* Debug-only sanity checks */
DEBUGASSERT(adc && minor >= 0 && minor < 100);
/* Initialize the touchscreen device driver instance */
priv->adc = adc; /* Save the ADC device handle */
/* Register the device as an input device */
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;
}
/* And return success. The hardware will be initialized as soon as the
* 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;
/* Are there pending TSD interrupts? */
if ((pending & ADC_TSD_ALLINTS) != 0)
{
/* Schedule sampling to occur by the interrupt bottom half on the
* worker thread.
*/
priv->pending = pending;
ret = sam_tsd_schedule(priv);
if (ret < 0)
{
ierr("ERROR: sam_tsd_schedule failed: %d\n", ret);
}
}
}
#endif /* CONFIG_SAMA5_ADC && CONFIG_SAMA5_TSD */