| /**************************************************************************** |
| * 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 */ |