/**************************************************************************** * drivers/input/mxt.c * * Copyright (C) 2014 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include /* Suppress verbose debug output so that we don't swamp the system */ #ifdef CONFIG_MXT_DISABLE_DEBUG_VERBOSE # undef CONFIG_DEBUG_VERBOSE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mxt.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Driver support ***********************************************************/ /* This format is used to construct the /dev/input[n] device driver path. It * defined here so that it will be used consistently in all places. */ #define DEV_FORMAT "/dev/input%d" #define DEV_NAMELEN 16 /* This is a value for the threshold that guarantees a big difference on the * first pendown (but can't overflow). */ #define INVALID_POSITION 0x1000 /* Get a 16-bit value in little endian order (not necessarily aligned). The * source data is in little endian order. The host byte order does not * matter in this case. */ #define MXT_GETUINT16(p) \ (((uint16_t)(((FAR uint8_t*)(p))[1]) << 8) | \ (uint16_t)(((FAR uint8_t*)(p))[0])) /**************************************************************************** * Private Types ****************************************************************************/ /* This enumeration describes the state of one contact. * * | * v * CONTACT_NONE (1) Touch * / (1) ^ (3) (2) Release * v \ (3) Event reported * CONTACT_NEW CONTACT_LOST * \ (3) ^ (2) * v / * CONTACT_REPORT * \ (1) ^ (3) * v / * CONTACT_MOVE * * NOTE: This state transition diagram is simplified. There are a few other * sneaky transitions to handle unexpected conditions. */ enum mxt_contact_e { CONTACT_NONE = 0, /* No contact */ CONTACT_NEW, /* New contact */ CONTACT_MOVE, /* Same contact, possibly different position */ CONTACT_REPORT, /* Contact reported */ CONTACT_LOST, /* Contact lost */ }; /* This structure describes the results of one MXT sample */ struct mxt_sample_s { uint8_t id; /* Sampled touch point ID */ uint8_t contact; /* Contact state (see enum mxt_contact_e) */ bool valid; /* True: x,y,pressure contain valid, sampled data */ uint16_t x; /* Measured X position */ uint16_t y; /* Measured Y position */ uint16_t lastx; /* Last reported X position */ uint16_t lasty; /* Last reported Y position */ uint8_t area; /* Contact area */ uint8_t pressure; /* Contact pressure */ }; /* This 7-bit 'info' data read from the MXT and that describes the * characteristics of the particular maXTouch chip */ struct mxt_info_s { uint8_t family; /* MXT family ID */ uint8_t variant; /* MXT variant ID */ uint8_t version; /* MXT version number */ uint8_t build; /* MXT build number */ uint8_t xsize; /* Matrix X size */ uint8_t ysize; /* Matrix Y size */ uint8_t nobjects; /* Number of objects */ }; #define MXT_INFO_SIZE 7 /* Describes the state of the MXT driver */ struct mxt_dev_s { /* These are the retained references to the I2C device and to the * lower half configuration data. */ FAR struct i2c_dev_s *i2c; FAR const struct mxt_lower_s *lower; /* This is the allocated array of object information */ FAR struct mxt_object_s *objtab; /* This is an allocated array of sample data, one for each possible touch */ FAR struct mxt_sample_s *sample; /* Last sampled touch point data */ uint8_t nwaiters; /* Number of threads waiting for MXT data */ uint8_t id; /* Current touch point ID */ uint8_t nslots; /* Number of slots */ uint8_t crefs; /* Reference count */ /* Cached parameters from object table */ #ifdef MXT_SUPPORT_T6 uint8_t t6id; /* T6 report ID */ #endif uint8_t t9idmin; /* T9 touch event report IDs */ uint8_t t9idmax; #ifdef CONFIG_MXT_BUTTONS uint8_t t19id; /* T19 button report ID */ #endif volatile bool event; /* True: An unreported event is buffered */ sem_t devsem; /* Manages exclusive access to this structure */ sem_t waitsem; /* Used to wait for the availability of data */ uint32_t frequency; /* Current I2C frequency */ char phys[64]; /* Device physical location */ struct mxt_info_s info; /* Configuration info read from device */ struct work_s work; /* Supports the interrupt handling "bottom half" */ /* 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'. */ #ifndef CONFIG_DISABLE_POLL struct pollfd *fds[CONFIG_MXT_NPOLLWAITERS]; #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* MXT register access */ static int mxt_getreg(FAR struct mxt_dev_s *priv, uint16_t regaddr, FAR uint8_t *buffer, size_t buflen); static int mxt_putreg(FAR struct mxt_dev_s *priv, uint16_t regaddr, FAR const uint8_t *buffer, size_t buflen); /* MXT object/message access */ static FAR struct mxt_object_s *mxt_object(FAR struct mxt_dev_s *priv, uint8_t type); static int mxt_getmessage(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg); static int mxt_putobject(FAR struct mxt_dev_s *priv, uint8_t type, uint8_t offset, uint8_t value); #if 0 /* Not used */ static int mxt_getobject(FAR struct mxt_dev_s *priv, uint8_t type, uint8_t offset, FAR uint8_t *value); #endif static int mxt_flushmsgs(FAR struct mxt_dev_s *priv); /* Poll support */ static void mxt_notify(FAR struct mxt_dev_s *priv); /* Touch event waiting */ static inline int mxt_checksample(FAR struct mxt_dev_s *priv); static inline int mxt_waitsample(FAR struct mxt_dev_s *priv); /* Interrupt handling/position sampling */ #ifdef CONFIG_MXT_BUTTONS static void mxt_button_event(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg); #endif static void mxt_touch_event(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg, int ndx); static void mxt_worker(FAR void *arg); static int mxt_interrupt(FAR const struct mxt_lower_s *lower, FAR void *arg); /* Character driver methods */ static int mxt_open(FAR struct file *filep); static int mxt_close(FAR struct file *filep); static ssize_t mxt_read(FAR struct file *filep, FAR char *buffer, size_t len); static int mxt_ioctl(FAR struct file *filep, int cmd, unsigned long arg); #ifndef CONFIG_DISABLE_POLL static int mxt_poll(FAR struct file *filep, struct pollfd *fds, bool setup); #endif /* Initialization */ static int mxt_getinfo(struct mxt_dev_s *priv); static int mxt_getobjtab(FAR struct mxt_dev_s *priv); static int mxt_hwinitialize(FAR struct mxt_dev_s *priv); /**************************************************************************** * Private Data ****************************************************************************/ /* This the vtable that supports the character driver interface */ static const struct file_operations mxt_fops = { mxt_open, /* open */ mxt_close, /* close */ mxt_read, /* read */ 0, /* write */ 0, /* seek */ mxt_ioctl /* ioctl */ #ifndef CONFIG_DISABLE_POLL , mxt_poll /* poll */ #endif }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: mxt_getreg ****************************************************************************/ static int mxt_getreg(FAR struct mxt_dev_s *priv, uint16_t regaddr, FAR uint8_t *buffer, size_t buflen) { struct i2c_msg_s msg[2]; uint8_t addrbuf[2]; int retries; int ret; /* Try up to three times to read the register */ for (retries = 1; retries <= 3; retries++) { ivdbg("retries=%d regaddr=%04x buflen=%d\n", retries, regaddr, buflen); /* Set up to write the address */ addrbuf[0] = regaddr & 0xff; addrbuf[1] = (regaddr >> 8) & 0xff; msg[0].addr = priv->lower->address; msg[0].flags = 0; msg[0].buffer = addrbuf; msg[0].length = 2; /* Followed by the read data */ msg[1].addr = priv->lower->address; msg[1].flags = I2C_M_READ; msg[1].buffer = buffer; msg[1].length = buflen; /* Read the register data. The returned value is the number messages * completed. */ ret = I2C_TRANSFER(priv->i2c, msg, 2); if (ret < 0) { #ifdef CONFIG_I2C_RESET /* Perhaps the I2C bus is locked up? Try to shake the bus free */ idbg("WARNING: I2C_TRANSFER failed: %d ... Resetting\n", ret); ret = up_i2creset(priv->i2c); if (ret < 0) { idbg("ERROR: up_i2creset failed: %d\n", ret); break; } #else idbg("ERROR: I2C_TRANSFER failed: %d\n", ret); #endif } else { /* The I2C transfer was successful... break out of the loop and * return the success indication. */ break; } } /* Return the last status returned by I2C_TRANSFER */ return ret; } /**************************************************************************** * Name: mxt_putreg ****************************************************************************/ static int mxt_putreg(FAR struct mxt_dev_s *priv, uint16_t regaddr, FAR const uint8_t *buffer, size_t buflen) { struct i2c_msg_s msg[2]; uint8_t addrbuf[2]; int retries; int ret; /* Try up to three times to read the register */ for (retries = 1; retries <= 3; retries++) { ivdbg("retries=%d regaddr=%04x buflen=%d\n", retries, regaddr, buflen); /* Set up to write the address */ addrbuf[0] = regaddr & 0xff; addrbuf[1] = (regaddr >> 8) & 0xff; msg[0].addr = priv->lower->address; msg[0].flags = 0; msg[0].buffer = addrbuf; msg[0].length = 2; /* Followed by the write data (with no repeated start) */ msg[1].addr = priv->lower->address; msg[1].flags = I2C_M_NORESTART; msg[1].buffer = (FAR uint8_t *)buffer; msg[1].length = buflen; /* Write the register data. The returned value is the number messages * completed. */ ret = I2C_TRANSFER(priv->i2c, msg, 2); if (ret < 0) { #ifdef CONFIG_I2C_RESET /* Perhaps the I2C bus is locked up? Try to shake the bus free */ idbg("WARNING: I2C_TRANSFER failed: %d ... Resetting\n", ret); ret = up_i2creset(priv->i2c); if (ret < 0) { idbg("ERROR: up_i2creset failed: %d\n", ret); } #else idbg("ERROR: I2C_TRANSFER failed: %d\n", ret); #endif } else { /* The I2C transfer was successful... break out of the loop and * return the success indication. */ break; } } /* Return the last status returned by I2C_TRANSFER */ return ret; } /**************************************************************************** * Name: mxt_object ****************************************************************************/ static FAR struct mxt_object_s *mxt_object(FAR struct mxt_dev_s *priv, uint8_t type) { struct mxt_object_s *object; int i; /* Search the object table for the entry matching the type */ for (i = 0; i < priv->info.nobjects; i++) { object = &priv->objtab[i]; if (object->type == type) { /* Found it.. return the pointer to the object structure */ return object; } } idbg("ERROR: Invalid object type: %d\n", type); return NULL; } /**************************************************************************** * Name: mxt_getmessage ****************************************************************************/ static int mxt_getmessage(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg) { struct mxt_object_s *object; uint16_t regaddr; object = mxt_object(priv, MXT_GEN_MESSAGE_T5); if (object == NULL) { idbg("ERROR: mxt_object failed\n"); return -EINVAL; } regaddr = MXT_GETUINT16(object->addr); return mxt_getreg(priv, regaddr, (FAR uint8_t *)msg, sizeof(struct mxt_msg_s)); } /**************************************************************************** * Name: mxt_putobject ****************************************************************************/ static int mxt_putobject(FAR struct mxt_dev_s *priv, uint8_t type, uint8_t offset, uint8_t value) { FAR struct mxt_object_s *object; uint16_t regaddr; object = mxt_object(priv, type); if (object == NULL || offset >= object->size + 1) { return -EINVAL; } regaddr = MXT_GETUINT16(object->addr); return mxt_putreg(priv, regaddr + offset, &value, 1); } /**************************************************************************** * Name: mxt_getobject ****************************************************************************/ #if 0 /* Not used */ static int mxt_getobject(FAR struct mxt_dev_s *priv, uint8_t type, uint8_t offset, FAR uint8_t *value) { FAR struct mxt_object_s *object; uint16_t regaddr; object = mxt_object(priv, type); if (object == NULL || offset >= object->size + 1) { return -EINVAL; } regaddr = MXT_GETUINT16(object->addr); return mxt_getreg(priv, regaddr + offset, value, 1); } #endif /**************************************************************************** * Name: mxt_flushmsgs * * Clear any pending messages be reading messages until there are no * pending messages. This will force the CHG pin to the high state and * prevent spurious initial interrupts. * ****************************************************************************/ static int mxt_flushmsgs(FAR struct mxt_dev_s *priv) { struct mxt_msg_s msg; int retries = 16; int ret; /* Read dummy message until there are no more to read (or until we have * tried 10 times). */ do { ret = mxt_getmessage(priv, &msg); if (ret < 0) { idbg("ERROR: mxt_getmessage failed: %d\n", ret); return ret; } } while (msg.id != 0xff && --retries > 0); /* Complain if we exceed the retry limit */ if (retries <= 0) { idbg("ERROR: Failed to clear messages: ID=%02x\n", msg.id); return -EBUSY; } return OK; } /**************************************************************************** * Name: mxt_notify ****************************************************************************/ static void mxt_notify(FAR struct mxt_dev_s *priv) { #ifndef CONFIG_DISABLE_POLL int i; #endif /* 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 maXTouch * is no longer available. */ sem_post(&priv->waitsem); } /* If there are threads waiting on poll() for maXTouch 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. */ #ifndef CONFIG_DISABLE_POLL for (i = 0; i < CONFIG_MXT_NPOLLWAITERS; i++) { struct pollfd *fds = priv->fds[i]; if (fds) { fds->revents |= POLLIN; ivdbg("Report events: %02x\n", fds->revents); sem_post(fds->sem); } } #endif } /**************************************************************************** * Name: mxt_checksample * * Description: * This function implements a test and clear of the priv->event flag. * Called only from mxt_waitsample. * * Assumptions: * - Scheduler must be locked to prevent the worker thread from running * while this thread runs. The sample data is, of course, updated from * the worker thread. * - 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. * ****************************************************************************/ static inline int mxt_checksample(FAR struct mxt_dev_s *priv) { /* Is there new maXTouch sample data available? */ if (priv->event) { /* Yes.. clear the flag and return success */ priv->event = false; return OK; } /* No.. return failure */ return -EAGAIN; } /**************************************************************************** * Name: mxt_waitsample * * Wait until sample data is available. Called only from mxt_read. * * Assumptions: * - Scheduler must be locked to prevent the worker thread from running * while this thread runs. The sample data is, of course, updated from * the worker thread. * ****************************************************************************/ static inline int mxt_waitsample(FAR struct mxt_dev_s *priv) { irqstate_t flags; int ret; /* Interrupts me be disabled when this is called to (1) prevent posting * of semaphores from interrupt handlers, and (2) to prevent sampled data * from changing until it has been reported. */ flags = irqsave(); /* 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. */ sem_post(&priv->devsem); /* Try to get the a sample... if we cannot, then wait on the semaphore * that is posted when new sample data is available. */ while (mxt_checksample(priv) < 0) { /* Wait for a change in the maXTouch state */ priv->nwaiters++; ret = sem_wait(&priv->waitsem); priv->nwaiters--; if (ret < 0) { /* If we are awakened by a signal, then we need to return * the failure now. */ DEBUGASSERT(errno == EINTR); ret = -EINTR; goto errout; } } /* Re-acquire the semaphore that manages mutually exclusive access to * the device structure. We may have to wait here. But we have our sample. * Interrupts and pre-emption will be re-enabled while we wait. */ ret = sem_wait(&priv->devsem); errout: /* Then re-enable interrupts. We might get interrupt here and there * could be a new sample. But no new threads will run because we still * have pre-emption disabled. */ irqrestore(flags); return ret; } /**************************************************************************** * Name: mxt_button_event ****************************************************************************/ #ifdef CONFIG_MXT_BUTTONS static void mxt_button_event(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg) { bool button; int i; /* REVISIT: Button inputs are currently ignored */ /* Buttons are active low and determined by the GPIO bit * settings in byte 0 of the message data: A button is * pressed if the corresponding bit is zero. */ for (i = 0; i < priv->lower->nbuttons; i++) { uint8_t bit = (MXT_GPIO0_MASK << i); /* Does this implementation support the button? */ if ((priv->lower->bmask & bit) != 0) { /* Yes.. get the button state */ button = (msg->body[0] & mask) == 0; /* Now what? */ } } } #endif /**************************************************************************** * Name: mxt_touch_event ****************************************************************************/ static void mxt_touch_event(FAR struct mxt_dev_s *priv, FAR struct mxt_msg_s *msg, int ndx) { FAR struct mxt_sample_s *sample; uint16_t x; uint16_t y; uint8_t area; uint8_t pressure; uint8_t status; /* Extract the 12-bit X and Y positions */ x = ((uint16_t)msg->body[1] << 4) | (((uint16_t)msg->body[3] >> 4) & 0x0f); y = ((uint16_t)msg->body[2] << 4) | (((uint16_t)msg->body[3] & 0x0f)); /* Swap X/Y as necessary */ if (priv->lower->swapxy) { uint16_t tmp = x; y = x; x = tmp; } /* Extract area pressure and status */ area = msg->body[4]; pressure = msg->body[5]; status = msg->body[0]; ivdbg("ndx=%u status=%02x pos(%u,%u) area=%u pressure=%u\n", ndx, status, x, y, area, pressure); /* The normal sequence that we would see for a touch would be something * like: * * 1. MXT_DETECT + MXT_PRESS * 2. MXT_DETECT + MXT_AMP * 3. MXT_DETECT + MXT_MOVE + MXT_AMP * 4. MXT_RELEASE * * So we really only need to check MXT_DETECT to drive this state machine. */ /* Is this a loss of contact? */ sample = &priv->sample[ndx]; if ((status & MXT_DETECT) == 0) { /* Ignore the event if there was no contact to be lost: * * CONTACT_NONE = No touch and loss-of-contact already reported * CONTACT_LOST = No touch and unreported loss-of-contact. */ if (sample->contact == CONTACT_NONE) { /* Return without posting any event */ return; } /* State is one of CONTACT_NEW, CONTACT_MOVE, CONTACT_REPORT or * CONTACT_LOST. * * NOTE: Here we do not check for these other states because there is * not much that can be done anyway. The transition to CONTACK_LOST * really only makes sense if the preceding state was CONTACT_REPORT. * If we were in (unreported) CONTACT_NEW or CONTACT_MOVE states, then * this will overwrite that event and it will not be reported. This * opens the possibility for contact lost reports when no contact was * ever reported. * * We could improve this be leaving the unreported states in place, * remembering that the contact was lost, and then reporting the loss- * of-contact after touch state is reported. */ sample->contact = CONTACT_LOST; /* Reset the last position so that we guarantee that the next position * will pass the thresholding test. */ sample->lastx = INVALID_POSITION; sample->lasty = INVALID_POSITION; } else { /* It is a touch event. If the last loss-of-contact event has not * been processed yet, then have to bump up the touch identifier and * hope that the client is smart enough to infer the loss-of-contact * event for the preceding touch. */ if (sample->contact == CONTACT_LOST) { priv->id++; } /* Save the measurements */ sample->x = x; sample->y = y; sample->area = area; sample->pressure = pressure; sample->valid = true; /* If this is not the first touch report, then report it as a move: * Same contact, same ID, but with a new, updated position. * The CONTACT_REPORT state means that a contacted has been detected, * but all contact events have been successfully reported. */ if (sample->contact == CONTACT_REPORT) { uint16_t xdiff; uint16_t ydiff; /* Not a new contact. Check if the new measurements represent a * non-trivial change in position. A trivial change is detected * by comparing the change in position since the last report * against configurable threshold values. * * REVISIT: Should a large change in pressure also generate a * event? */ xdiff = x > sample->lastx ? (x - sample->lastx) : (sample->lastx - x); ydiff = y > sample->lasty ? (y - sample->lasty) : (sample->lasty - y); /* Check the thresholds */ if (xdiff >= CONFIG_MXT_THRESHX || ydiff >= CONFIG_MXT_THRESHY) { /* Report a contact move event. This state will be set back * to CONTACT_REPORT after it been reported. */ sample->contact = CONTACT_MOVE; /* Update the last position for next threshold calculations */ sample->lastx = x; sample->lasty = y; } else { /* Bail without reporting anything for this event */ return; } } /* If we have seen this contact before but it has not yet been * reported, then do nothing other than overwrite the positional * data. * * This the state must be one of CONTACT_NONE or CONTACT_LOST (see * above) and we have a new contact with a new ID. */ else if (sample->contact != CONTACT_NEW && sample->contact != CONTACT_MOVE) { /* First contact. Save the contact event and assign a new * ID to the contact. */ sample->contact = CONTACT_NEW; sample->id = priv->id++; /* Update the last position for next threshold calculations */ sample->lastx = x; sample->lasty = y; /* This state will be set to CONTACT_REPORT after it * been reported. */ } } /* Indicate the availability of new sample data for this ID and notify * any waiters that new maXTouch data is available */ priv->event = true; mxt_notify(priv); } /**************************************************************************** * Name: mxt_worker ****************************************************************************/ static void mxt_worker(FAR void *arg) { FAR struct mxt_dev_s *priv = (FAR struct mxt_dev_s *)arg; FAR const struct mxt_lower_s *lower; struct mxt_msg_s msg; uint8_t id; int retries; int ret; ASSERT(priv != NULL); /* Get a pointer the callbacks for convenience (and so the code is not so * ugly). */ lower = priv->lower; DEBUGASSERT(lower != NULL); /* Get exclusive access to the MXT driver data structure */ do { ret = sem_wait(&priv->devsem); DEBUGASSERT(ret == 0 || errno == EINTR); } while (ret < 0); /* Loop, processing each message from the maXTouch */ retries = 0; do { /* Retrieve the next message from the maXTouch */ ret = mxt_getmessage(priv, &msg); if (ret < 0) { idbg("ERROR: mxt_getmessage failed: %d\n", ret); goto errout_with_semaphore; } id = msg.id; #ifdef MXT_SUPPORT_T6 /* Check for T6 */ if (id == priv->t6id) { uint32_t chksum; int status; status = msg.body[0]; chksum = (uint32_t)msg.body[1] | ((uint32_t)msg.body[2] << 8) | ((uint32_t)msg.body[3] << 16); ivdbg("T6: status: %02x checksum: %06lx\n", status, (unsigned long)chksum); retries = 0; } else #endif /* Check for T9 */ if (id >= priv->t9idmin && id <= priv->t9idmax) { mxt_touch_event(priv, &msg, id - priv->t9idmin); retries = 0; } #ifdef CONFIG_MXT_BUTTONS /* Check for T19 */ else if (msg.id == priv->t19id) { mxt_button_event(priv, &msg); retries = 0; } #endif /* 0xff marks the end of the messages; any other message IDs are ignored * (after complaining a little). */ else if (msg.id != 0xff) { ivdbg("Ignored: id=%u message={%02x %02x %02x %02x %02x %02x %02x}\n", msg.id, msg.body[0], msg.body[1], msg.body[2], msg.body[3], msg.body[4], msg.body[5], msg.body[6]); retries++; } } while (id != 0xff && retries < 16); errout_with_semaphore: /* Release our lock on the MXT device */ sem_post(&priv->devsem); /* Acknowledge and re-enable maXTouch interrupts */ MXT_CLEAR(lower); MXT_ENABLE(lower); } /**************************************************************************** * Name: mxt_interrupt ****************************************************************************/ static int mxt_interrupt(FAR const struct mxt_lower_s *lower, FAR void *arg) { FAR struct mxt_dev_s *priv = (FAR struct mxt_dev_s *)arg; int ret; /* Get a pointer the callbacks for convenience (and so the code is not so * ugly). */ DEBUGASSERT(lower != NULL && priv != NULL); /* Disable further interrupts */ MXT_DISABLE(lower); /* Transfer processing to the worker thread. Since maXTouch 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, mxt_worker, priv, 0); if (ret != 0) { illdbg("Failed to queue work: %d\n", ret); } /* Clear any pending interrupts and return success */ lower->clear(lower); return OK; } /**************************************************************************** * Name: mxt_open ****************************************************************************/ static int mxt_open(FAR struct file *filep) { FAR struct inode *inode; FAR struct mxt_dev_s *priv; uint8_t tmp; int ret; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct mxt_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = sem_wait(&priv->devsem); if (ret < 0) { /* This should only happen if the wait was cancelled by an signal */ DEBUGASSERT(errno == EINTR); return -EINTR; } /* Increment the reference count */ tmp = priv->crefs + 1; if (tmp == 0) { /* More than 255 opens; uint8_t overflows to zero */ idbg("ERROR: Too many opens: %d\n", priv->crefs); ret = -EMFILE; goto errout_with_sem; } /* When the reference increments to 1, this is the first open event * on the driver.. and an opportunity to do any one-time initialization. */ if (tmp == 1) { /* Touch enable */ ret = mxt_putobject(priv, MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0x83); if (ret < 0) { idbg("ERROR: Failed to enable touch: %d\n", ret); goto errout_with_sem; } /* Clear any pending messages by reading all messages. This will * force the CHG interrupt pin to the high state and prevent spurious * interrupts when they are enabled. */ ret = mxt_flushmsgs(priv); if (ret < 0) { idbg("ERROR: mxt_flushmsgs failed: %d\n", ret); mxt_putobject(priv, MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0); goto errout_with_sem; } /* Enable touch interrupts */ MXT_ENABLE(priv->lower); } /* Save the new open count on success */ priv->crefs = tmp; errout_with_sem: sem_post(&priv->devsem); return ret; } /**************************************************************************** * Name: mxt_close ****************************************************************************/ static int mxt_close(FAR struct file *filep) { FAR struct inode *inode; FAR struct mxt_dev_s *priv; int ret; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct mxt_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = sem_wait(&priv->devsem); if (ret < 0) { /* This should only happen if the wait was cancelled by an signal */ DEBUGASSERT(errno == EINTR); return -EINTR; } /* Decrement the reference count unless it would decrement a negative * value. When the count decrements to zero, there are no further * open references to the driver. */ if (priv->crefs >= 1) { if (--priv->crefs < 1) { /* Disable touch interrupts */ MXT_ENABLE(priv->lower); /* Touch disable */ ret = mxt_putobject(priv, MXT_TOUCH_MULTI_T9, MXT_TOUCH_CTRL, 0); if (ret < 0) { idbg("ERROR: Failed to disable touch: %d\n", ret); } } } sem_post(&priv->devsem); return OK; } /**************************************************************************** * Name: mxt_read ****************************************************************************/ static ssize_t mxt_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct inode *inode; FAR struct mxt_dev_s *priv; ssize_t samplesize; int ncontacts; int ret; int i; int j; DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct mxt_dev_s *)inode->i_private; /* Verify that the caller has provided a buffer large enough to receive * the touch data. */ if (len < SIZEOF_TOUCH_SAMPLE_S(1)) { /* We could provide logic to break up a touch report into segments and * handle smaller reads... but why? */ return -ENOSYS; } /* Get exclusive access to the driver data structure */ ret = sem_wait(&priv->devsem); if (ret < 0) { /* This should only happen if the wait was cancelled by an signal */ DEBUGASSERT(errno == EINTR); return -EINTR; } /* Locking the scheduler will prevent the worker thread from running * until we finish here. */ sched_lock(); /* Try to read sample data. */ ret = mxt_checksample(priv); if (ret < 0) { /* Sample data is not available now. We would ave to wait to get * receive sample data. If the user has specified the O_NONBLOCK * option, then just return an error. */ if (filep->f_oflags & O_NONBLOCK) { ret = -EAGAIN; goto errout; } /* Wait for sample data */ ret = mxt_waitsample(priv); if (ret < 0) { /* We might have been awakened by a signal */ goto errout; } } /* In any event, we now have sampled maXTouch data that we can report * to the caller. First, count the number of valid contacts. */ samplesize = 0; ncontacts = 0; for (i = 0; i < priv->nslots; i++) { FAR struct mxt_sample_s *sample = &priv->sample[i]; /* Do we need to report this? We need to report the event if * it is CONTACT_LOST or CONTACT_REPORT or CONTACT_MOVE (with new, * valid positional data). */ if (sample->contact == CONTACT_LOST || sample->contact == CONTACT_NEW || sample->contact == CONTACT_MOVE) { int newcount = ncontacts + 1; ssize_t newsize = SIZEOF_TOUCH_SAMPLE_S(newcount); /* Would this sample exceed the buffer size provided by the * caller? */ if (newsize > len) { /* Yes.. break out of the loop using the previous size and * count. */ break; } /* Save the new size and count */ ncontacts = newcount; samplesize = newsize; } } /* Did we find any valid samples? */ if (ncontacts > 0) { FAR struct touch_sample_s *report = (FAR struct touch_sample_s *)buffer; /* Yes, copy the sample data into the user bufer */ memset(report, 0, SIZEOF_TOUCH_SAMPLE_S(ncontacts)); report->npoints = ncontacts; for (i = 0, j= 0; i < priv->nslots && j < ncontacts; i++) { FAR struct mxt_sample_s *sample = &priv->sample[i]; /* Do we need to report this? We need to report the event if * it is CONTACT_LOST or CONTACT_REPORT or CONTACT_MOVE (with new, * valid positional data). */ if (sample->contact == CONTACT_LOST || sample->contact == CONTACT_NEW || sample->contact == CONTACT_MOVE) { /* Yes.. transfer the sample data */ FAR struct touch_point_s *point = &report->point[j]; j++; /* REVISIT: height and width are not set, area is * not used. */ point->id = sample->id; point->x = sample->x; point->y = sample->y; point->pressure = sample->pressure; /* Report the appropriate flags */ if (sample->contact == CONTACT_LOST) { /* The contact was lost. 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) { point->flags = TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID | TOUCH_PRESSURE_VALID; } else { point->flags = TOUCH_UP | TOUCH_ID_VALID; } /* Change to CONTACT_NONE to indicate that the sample * has been reported. From here it can change only * to CONTACT_REPORT (with a new ID). */ sample->contact = CONTACT_NONE; } else { /* We have contact. Is it the first contact? */ if (sample->contact == CONTACT_NEW) { /* Yes.. first contact. */ point->flags = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID; } else /* if (sample->contact == CONTACT_MOVE) */ { /* No.. then it must be movement of the same contact */ point->flags = TOUCH_MOVE | TOUCH_ID_VALID | TOUCH_POS_VALID; } /* Change to CONTACT_REPORT to indicate that the sample * has been reported. From here is can change to * CONTACT_LOST (same ID), CONTACT_MOVE (same ID) or back * to CONTACT_NEW (new ID). */ sample->contact = CONTACT_REPORT; /* A pressure measurement of zero means that pressure is not * available */ if (point->pressure != 0) { point->flags |= TOUCH_PRESSURE_VALID; } } /* In any case, the sample data has been reported and is no * longer valid. */ sample->valid = false; } } } ret = samplesize; errout: sched_unlock(); sem_post(&priv->devsem); return ret; } /**************************************************************************** * Name:mxt_ioctl ****************************************************************************/ static int mxt_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode; FAR struct mxt_dev_s *priv; int ret; ivdbg("cmd: %d arg: %ld\n", cmd, arg); DEBUGASSERT(filep); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct mxt_dev_s *)inode->i_private; /* Get exclusive access to the driver data structure */ ret = sem_wait(&priv->devsem); if (ret < 0) { /* This should only happen if the wait was cancelled by an signal */ DEBUGASSERT(errno == EINTR); return -EINTR; } /* Process the IOCTL by command */ switch (cmd) { case TSIOC_SETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ { FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); DEBUGASSERT(priv->lower != NULL && ptr != NULL); priv->frequency = I2C_SETFREQUENCY(priv->i2c, *ptr); } break; case TSIOC_GETFREQUENCY: /* arg: Pointer to uint32_t frequency value */ { FAR uint32_t *ptr = (FAR uint32_t *)((uintptr_t)arg); DEBUGASSERT(priv->lower != NULL && ptr != NULL); *ptr = priv->frequency; } break; default: ret = -ENOTTY; break; } sem_post(&priv->devsem); return ret; } /**************************************************************************** * Name: mxt_poll ****************************************************************************/ #ifndef CONFIG_DISABLE_POLL static int mxt_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { FAR struct inode *inode; FAR struct mxt_dev_s *priv; int ret; int i; ivdbg("setup: %d\n", (int)setup); DEBUGASSERT(filep && fds); inode = filep->f_inode; DEBUGASSERT(inode && inode->i_private); priv = (FAR struct mxt_dev_s *)inode->i_private; /* Are we setting up the poll? Or tearing it down? */ ret = sem_wait(&priv->devsem); if (ret < 0) { /* This should only happen if the wait was cancelled by an signal */ DEBUGASSERT(errno == EINTR); return -EINTR; } if (setup) { /* Ignore waits that do not include POLLIN */ if ((fds->events & POLLIN) == 0) { idbg("Missing POLLIN: revents: %08x\n", fds->revents); ret = -EDEADLK; goto errout; } /* This is a request to set up the poll. Find an available * slot for the poll structure reference */ for (i = 0; i < CONFIG_MXT_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_MXT_NPOLLWAITERS) { idbg("No availabled slot found: %d\n", i); fds->priv = NULL; ret = -EBUSY; goto errout; } /* Should we immediately notify on any of the requested events? */ if (priv->event) { mxt_notify(priv); } } else if (fds->priv) { /* This is a request to tear down the poll. */ struct pollfd **slot = (struct pollfd **)fds->priv; DEBUGASSERT(slot != NULL); /* Remove all memory of the poll setup */ *slot = NULL; fds->priv = NULL; } errout: sem_post(&priv->devsem); return ret; } #endif /**************************************************************************** * Name: mxt_getinfo ****************************************************************************/ static int mxt_getinfo(struct mxt_dev_s *priv) { int ret; /* Read 7-byte information block starting at address MXT_INFO */ ret = mxt_getreg(priv, MXT_INFO, (FAR uint8_t *)&priv->info, sizeof(struct mxt_info_s)); if (ret < 0) { idbg("ERROR: mxt_getreg failed: %d\n", ret); return ret; } return OK; } /**************************************************************************** * Name: mxt_getobjtab ****************************************************************************/ static int mxt_getobjtab(FAR struct mxt_dev_s *priv) { FAR struct mxt_object_s *object; size_t tabsize; uint8_t idmin; uint8_t idmax; uint8_t id; int ret; int i; /* Read the size of the object table */ tabsize = priv->info.nobjects * sizeof(struct mxt_object_s); ret = mxt_getreg(priv, MXT_OBJECT_START, (FAR uint8_t *)priv->objtab, tabsize); if (ret < 0) { idbg("ERROR: Failed to object table size: %d\n", ret); return ret; } /* Search through the object table. Find the values associated with * certain object types and save those ID.Valid report IDs start at ID=1. */ for (i = 0, id = 1; i < priv->info.nobjects; i++) { object = &priv->objtab[i]; if (object->nids > 0) { idmin = id; id += object->nids * (object->ninstances + 1); idmax = id - 1; } else { idmin = 0; idmax = 0; } ivdbg("%2d. type %2d addr %04x size: %d instances: %d IDs: %u-%u\n", i, object->type, MXT_GETUINT16(object->addr), object->size + 1, object->ninstances + 1, idmin, idmax); switch (object->type) { #ifdef MXT_SUPPORT_T6 case MXT_GEN_COMMAND_T6: priv->t6id = idmin; break; #endif case MXT_TOUCH_MULTI_T9: priv->t9idmin = idmin; priv->t9idmax = idmax; break; #ifdef CONFIG_MXT_BUTTONS case MXT_SPT_GPIOPWM_T19: priv->t19id = idmin; break; #endif default: break; } } return OK; } /**************************************************************************** * Name: mxt_hwinitialize ****************************************************************************/ static int mxt_hwinitialize(FAR struct mxt_dev_s *priv) { struct mxt_info_s *info = &priv->info; unsigned int nslots; uint8_t regval; int ret; /* Set the selected I2C frequency */ priv->frequency = I2C_SETFREQUENCY(priv->i2c, priv->lower->frequency); /* Read the info registers from the device */ ret = mxt_getinfo(priv); if (ret < 0) { idbg("ERROR: Failed to read info registers: %d\n", ret); return ret; } /* Allocate memory for the object table */ priv->objtab = kmm_zalloc(info->nobjects * sizeof(struct mxt_object_s)); if (priv->objtab == NULL) { idbg("ERROR: Failed to allocate object table\n"); return -ENOMEM; } /* Get object table information */ ret = mxt_getobjtab(priv); if (ret < 0) { goto errout_with_objtab; } /* Perform a soft reset */ ret = mxt_putobject(priv, MXT_GEN_COMMAND_T6, MXT_COMMAND_RESET, 1); if (ret < 0) { idbg("ERROR: Soft reset failed: %d\n", ret); goto errout_with_objtab; } usleep(MXT_RESET_TIME); /* Update matrix size in the info structure */ ret = mxt_getreg(priv, MXT_MATRIX_X_SIZE, (FAR uint8_t *)®val, 1); if (ret < 0) { idbg("ERROR: Failed to get X size: %d\n", ret); goto errout_with_objtab; } info->xsize = regval; ret = mxt_getreg(priv, MXT_MATRIX_Y_SIZE, (FAR uint8_t *)®val, 1); if (ret < 0) { idbg("ERROR: Failed to get Y size: %d\n", ret); goto errout_with_objtab; } info->ysize = regval; ivdbg("Family: %u variant: %u version: %u.%u.%02x\n", info->family, info->variant, info->version >> 4, info->version & 0x0f, info->build); ivdbg("Matrix size: (%u,%u) objects: %u\n", info->xsize, info->ysize, info->nobjects); /* How many multi touch "slots" */ nslots = priv->t9idmax - priv->t9idmin + 1; DEBUGASSERT(nslots > 0 && nslots < 256); priv->nslots = nslots; /* Allocate a place to hold sample data for each slot */ priv->sample = (FAR struct mxt_sample_s *) kmm_zalloc(nslots * sizeof(struct mxt_sample_s)); if (priv->sample == NULL) { idbg("ERROR: Failed to allocate object table\n"); goto errout_with_objtab; } return OK; /* Error exits */ errout_with_objtab: kmm_free(priv->objtab); priv->objtab = NULL; return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: mxt_register * * Description: * Configure the maXTouch to use the provided I2C device instance. This * will register the driver as /dev/inputN where N is the minor device * number * * Input Parameters: * i2c - An I2C driver instance * lower - Persistent board configuration data * minor - The input device minor number * * Returned Value: * Zero is returned on success. Otherwise, a negated errno value is * returned to indicate the nature of the failure. * ****************************************************************************/ int mxt_register(FAR struct i2c_dev_s *i2c, FAR const struct mxt_lower_s * const lower, int minor) { FAR struct mxt_dev_s *priv; char devname[DEV_NAMELEN]; int ret; ivdbg("i2c: %p minor: %d\n", i2c, minor); /* Debug-only sanity checks */ DEBUGASSERT(i2c != NULL && lower != NULL && minor >= 0 && minor < 100); /* Create and initialize a maXTouch device driver instance */ priv = (FAR struct mxt_dev_s *)kmm_zalloc(sizeof(struct mxt_dev_s)); if (priv == NULL) { idbg("ERROR: Failed allocate device structure\n"); return -ENOMEM; } /* Initialize the ADS7843E device driver instance */ memset(priv, 0, sizeof(struct mxt_dev_s)); priv->i2c = i2c; /* Save the SPI device handle */ priv->lower = lower; /* Save the board configuration */ sem_init(&priv->devsem, 0, 1); /* Initialize device semaphore */ sem_init(&priv->waitsem, 0, 0); /* Initialize event wait semaphore */ /* Make sure that interrupts are disabled */ MXT_CLEAR(lower); MXT_DISABLE(lower); /* Attach the interrupt handler */ ret = MXT_ATTACH(lower, mxt_interrupt, priv); if (ret < 0) { idbg("Failed to attach interrupt\n"); goto errout_with_priv; } /* Configure the MXT hardware */ ret = mxt_hwinitialize(priv); if (ret < 0) { idbg("ERROR: mxt_hwinitialize failed: %d\n", ret); goto errout_with_irq; } /* Register the device as an input device */ (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, minor); ivdbg("Registering %s\n", devname); ret = register_driver(devname, &mxt_fops, 0666, priv); if (ret < 0) { idbg("register_driver() failed: %d\n", ret); goto errout_with_hwinit; } /* And return success. MXT interrupts will not be enable until the * MXT device has been opened (see mxt_open). */ return OK; /* Error clean-up exits */ errout_with_hwinit: kmm_free(priv->objtab); kmm_free(priv->sample); errout_with_irq: MXT_DETACH(lower); errout_with_priv: sem_destroy(&priv->devsem); sem_destroy(&priv->waitsem); kmm_free(priv); return ret; }