nuttx/drivers/input/gt9xx.c
Lee Lup Yuen 6de5a862cd drivers/input: Add driver for Goodix GT9XX Touch Panel
This PR adds the driver for Goodix GT9XX I2C Touch Panel, which will be called by PINE64 PinePhone.

Our driver follows the design of the NuttX Driver for [Cypress MBR3108](https://github.com/apache/nuttx/blob/master/drivers/input/cypress_mbr3108.c).

We have documented our driver here: ["NuttX Touch Panel Driver for PinePhone"](https://lupyuen.github.io/articles/touch2#appendix-nuttx-touch-panel-driver-for-pinephone)

`drivers/input/Kconfig`: Added option `INPUT_GT9XX` to select GT9XX Driver

`drivers/input/Make.defs`: Added GT9XX Driver to Makefile

`drivers/input/gt9xx.c`, `gt9xx.h`: I2C Driver for GT9XX Touch Panel
2023-01-13 12:19:53 +08:00

957 lines
24 KiB
C

/****************************************************************************
* drivers/input/gt9xx.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* Reference:
* "NuttX RTOS for PinePhone: Touch Panel"
* https://lupyuen.github.io/articles/touch2
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <poll.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/input/touchscreen.h>
#include <nuttx/input/gt9xx.h>
/****************************************************************************
* Pre-Processor Definitions
****************************************************************************/
/* Default I2C Frequency is 400 kHz */
#ifndef CONFIG_INPUT_GT9XX_I2C_FREQUENCY
# define CONFIG_INPUT_GT9XX_I2C_FREQUENCY 400000
#endif
/* Default Number of Poll Waiters is 1 */
#ifndef CONFIG_INPUT_GT9XX_NPOLLWAITERS
# define CONFIG_INPUT_GT9XX_NPOLLWAITERS 1
#endif
/* I2C Registers for Goodix GT9XX Touch Panel */
#define GTP_REG_VERSION 0x8140 /* Product ID */
#define GTP_READ_COOR_ADDR 0x814e /* Touch Panel Status */
#define GTP_POINT1 0x8150 /* Touch Point 1 */
/****************************************************************************
* Private Types
****************************************************************************/
/* Touch Panel Device */
struct gt9xx_dev_s
{
/* I2C bus and address for device */
struct i2c_master_s *i2c;
uint8_t addr;
/* Callback for Board-Specific Operations */
const struct gt9xx_board_s *board;
/* Device State */
mutex_t devlock; /* Mutex to prevent concurrent reads */
uint8_t cref; /* Reference Counter for device */
bool int_pending; /* True if a Touch Interrupt is pending processing */
uint16_t x; /* X Coordinate of Last Touch Point */
uint16_t y; /* Y Coordinate of Last Touch Point */
uint8_t flags; /* Touch Up or Touch Down for Last Touch Point */
/* Poll Waiters for device */
struct pollfd *fds[CONFIG_INPUT_GT9XX_NPOLLWAITERS];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int gt9xx_open(FAR struct file *filep);
static int gt9xx_close(FAR struct file *filep);
static ssize_t gt9xx_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static int gt9xx_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
/****************************************************************************
* Private Data
****************************************************************************/
/* File Operations for Touch Panel */
static const struct file_operations g_gt9xx_fileops =
{
gt9xx_open, /* open */
gt9xx_close, /* close */
gt9xx_read, /* read */
NULL, /* write */
NULL, /* seek */
NULL, /* ioctl */
NULL, /* truncate */
NULL, /* mmap */
gt9xx_poll /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, NULL /* unlink */
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: gt9xx_i2c_read
*
* Description:
* Read a Touch Panel Register over I2C.
*
* Input Parameters:
* dev - Touch Panel Device
* reg - I2C Register to be read
* buf - Receive Buffer
* buflen - Number of bytes to be read
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_i2c_read(FAR struct gt9xx_dev_s *dev,
uint16_t reg,
uint8_t *buf,
size_t buflen)
{
int ret;
/* Send the Register Address, MSB first */
uint8_t regbuf[2] =
{
reg >> 8, /* First Byte: MSB */
reg & 0xff /* Second Byte: LSB */
};
/* Compose the I2C Messages */
struct i2c_msg_s msgv[2] =
{
{
/* Send the I2C Register Address */
.frequency = CONFIG_INPUT_GT9XX_I2C_FREQUENCY,
.addr = dev->addr,
.flags = 0,
.buffer = regbuf,
.length = sizeof(regbuf)
},
{
/* Receive the I2C Register Values */
.frequency = CONFIG_INPUT_GT9XX_I2C_FREQUENCY,
.addr = dev->addr,
.flags = I2C_M_READ,
.buffer = buf,
.length = buflen
}
};
const int msgv_len = sizeof(msgv) / sizeof(msgv[0]);
iinfo("reg=0x%x, buflen=%ld\n", reg, buflen);
DEBUGASSERT(dev && dev->i2c && buf);
/* Execute the I2C Transfer */
ret = I2C_TRANSFER(dev->i2c, msgv, msgv_len);
if (ret < 0)
{
ierr("I2C Read failed: %d\n", ret);
return ret;
}
#ifdef CONFIG_DEBUG_INPUT_INFO
iinfodumpbuffer("gt9xx_i2c_read", buf, buflen);
#endif /* CONFIG_DEBUG_INPUT_INFO */
return OK;
}
/****************************************************************************
* Name: gt9xx_i2c_write
*
* Description:
* Write to a Touch Panel Register over I2C.
*
* Input Parameters:
* dev - Touch Panel Device
* reg - I2C Register to be written
* val - Value to be written
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_i2c_write(FAR struct gt9xx_dev_s *dev,
uint16_t reg,
uint8_t val)
{
int ret;
/* Send the Register Address, MSB first */
uint8_t regbuf[2] =
{
reg >> 8, /* First Byte: MSB */
reg & 0xff /* Second Byte: LSB */
};
/* Send the Register Value */
uint8_t buf[1] =
{
val /* Value to be written */
};
/* Compose the I2C Messages */
struct i2c_msg_s msgv[2] =
{
{
/* Send the I2C Register Address */
.frequency = CONFIG_INPUT_GT9XX_I2C_FREQUENCY,
.addr = dev->addr,
.flags = 0,
.buffer = regbuf,
.length = sizeof(regbuf)
},
{
/* Send the I2C Register Value */
.frequency = CONFIG_INPUT_GT9XX_I2C_FREQUENCY,
.addr = dev->addr,
.flags = I2C_M_NOSTART,
.buffer = buf,
.length = sizeof(buf)
}
};
const int msgv_len = sizeof(msgv) / sizeof(msgv[0]);
iinfo("reg=0x%x, val=%d\n", reg, val);
DEBUGASSERT(dev && dev->i2c);
/* Execute the I2C Transfer */
ret = I2C_TRANSFER(dev->i2c, msgv, msgv_len);
if (ret < 0)
{
ierr("I2C Write failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: gt9xx_probe_device
*
* Description:
* Read the Product ID from the Touch Panel over I2C.
*
* Input Parameters:
* dev - Touch Panel Device
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_probe_device(FAR struct gt9xx_dev_s *dev)
{
int ret;
uint8_t id[4];
/* Read the Product ID */
ret = gt9xx_i2c_read(dev, GTP_REG_VERSION, id, sizeof(id));
if (ret < 0)
{
ierr("I2C Probe failed: %d\n", ret);
return ret;
}
/* For GT917S: Product ID will be 39 31 37 53, i.e. "917S" */
#ifdef CONFIG_DEBUG_INPUT_INFO
iinfodumpbuffer("gt9xx_probe_device", id, sizeof(id));
#endif /* CONFIG_DEBUG_INPUT_INFO */
return OK;
}
/****************************************************************************
* Name: gt9xx_set_status
*
* Description:
* Set the Touch Panel Status over I2C.
*
* Input Parameters:
* dev - Touch Panel Device
* status - Status value to be set
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_set_status(FAR struct gt9xx_dev_s *dev, uint8_t status)
{
int ret;
iinfo("status=%d\n", status);
DEBUGASSERT(dev);
/* Write to the Status Register over I2C */
ret = gt9xx_i2c_write(dev, GTP_READ_COOR_ADDR, status);
if (ret < 0)
{
ierr("Set Status failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: gt9xx_read_touch_data
*
* Description:
* Read a Touch Sample from Touch Panel. Returns either 0 or 1
* Touch Points.
*
* Input Parameters:
* dev - Touch Panel Device
* sample - Returned Touch Sample (0 or 1 Touch Points)
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_read_touch_data(FAR struct gt9xx_dev_s *dev,
FAR struct touch_sample_s *sample)
{
uint8_t status[1];
uint8_t status_code;
uint8_t touched_points;
uint8_t touch[6];
uint16_t x;
uint16_t y;
uint8_t flags;
int ret;
/* Erase the Touch Sample and Touch Point */
iinfo("\n");
DEBUGASSERT(dev && sample);
memset(sample, 0, sizeof(*sample));
/* Read the Touch Panel Status */
ret = gt9xx_i2c_read(dev, GTP_READ_COOR_ADDR, status, sizeof(status));
if (ret < 0)
{
ierr("Read Touch Panel Status failed: %d\n", ret);
return ret;
}
/* Decode the Status Code and the Touched Points */
status_code = status[0] & 0x80;
touched_points = status[0] & 0x0f;
/* If Touch Panel Status is OK and Touched Points is 1 or more */
if (status_code != 0 && touched_points >= 1)
{
/* Read the First Touch Point (6 bytes) */
ret = gt9xx_i2c_read(dev, GTP_POINT1, touch, sizeof(touch));
if (ret < 0)
{
ierr("Read Touch Point failed: %d\n", ret);
return ret;
}
/* Decode the Touch Coordinates */
x = touch[0] + (touch[1] << 8);
y = touch[2] + (touch[3] << 8);
/* Return the Touch Coordinates as Touch Down */
flags = TOUCH_DOWN | TOUCH_ID_VALID | TOUCH_POS_VALID;
sample->npoints = 1;
sample->point[0].id = 0;
sample->point[0].x = x;
sample->point[0].y = y;
sample->point[0].flags = flags;
iinfo("touch down x=%d, y=%d\n", x, y);
}
/* Set the Touch Panel Status to 0 */
ret = gt9xx_set_status(dev, 0);
if (ret < 0)
{
ierr("Set Touch Panel Status failed: %d\n", ret);
return ret;
}
return OK;
}
/****************************************************************************
* Name: gt9xx_read
*
* Description:
* Read a Touch Sample from Touch Panel. Returns either 0 or 1
* Touch Points.
*
* Input Parameters:
* dev - Touch Panel Device
* buffer - Returned Touch Sample (0 or 1 Touch Points)
* buflen - Size of buffer
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static ssize_t gt9xx_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct gt9xx_dev_s *priv;
struct touch_sample_s sample;
const size_t outlen = sizeof(sample);
irqstate_t flags;
int ret;
/* Returned Touch Sample will have 0 or 1 Touch Points */
iinfo("buflen=%ld\n", buflen);
if (buflen < outlen)
{
ierr("Buffer should be at least %ld bytes, got %ld bytes\n",
outlen, buflen);
return -EINVAL;
}
/* Get the Touch Panel Device */
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = inode->i_private;
/* Begin Mutex: Lock to prevent concurrent reads */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
return ret;
}
ret = -EINVAL;
/* If waiting for Touch Up, return the Last Touch Point as Touch Up */
if (priv->flags & TOUCH_DOWN)
{
/* Begin Critical Section */
flags = enter_critical_section();
/* Mark the Last Touch Point as Touch Up */
priv->flags = TOUCH_UP | TOUCH_ID_VALID | TOUCH_POS_VALID;
/* End Critical Section */
leave_critical_section(flags);
/* Return the Last Touch Point, changed to Touch Up */
memset(&sample, 0, sizeof(sample));
sample.npoints = 1;
sample.point[0].id = 0;
sample.point[0].x = priv->x;
sample.point[0].y = priv->y;
sample.point[0].flags = priv->flags;
memcpy(buffer, &sample, sizeof(sample));
ret = OK;
iinfo("touch up x=%d, y=%d\n", priv->x, priv->y);
}
else
{
/* Otherwise read the Touch Point over I2C */
ret = gt9xx_read_touch_data(priv, &sample);
/* Skip duplicates */
if (sample.npoints >= 1 &&
priv->x == sample.point[0].x &&
priv->y == sample.point[0].y)
{
memset(&sample, 0, sizeof(sample));
sample.npoints = 0;
iinfo("skip duplicate x=%d, y=%d\n", priv->x, priv->y);
}
/* Return the Touch Point */
memcpy(buffer, &sample, sizeof(sample));
/* Begin Critical Section */
flags = enter_critical_section();
/* Clear the Interrupt Pending Flag */
priv->int_pending = false;
/* Remember the Last Touch Point */
if (sample.npoints >= 1)
{
priv->x = sample.point[0].x;
priv->y = sample.point[0].y;
priv->flags = sample.point[0].flags;
}
/* End Critical Section */
leave_critical_section(flags);
}
/* End Mutex: Unlock to allow next read */
nxmutex_unlock(&priv->devlock);
return (ret < 0) ? ret : outlen;
}
/****************************************************************************
* Name: gt9xx_open
*
* Description:
* Open the Touch Panel Device. If this is the first open, we power on
* the Touch Panel, probe for the Touch Panel and enable Touch Panel
* Interrupts.
*
* Input Parameters:
* filep - File Struct for Touch Panel
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_open(FAR struct file *filep)
{
FAR struct inode *inode;
FAR struct gt9xx_dev_s *priv;
unsigned int use_count;
int ret;
/* Get the Touch Panel Device */
iinfo("\n");
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = inode->i_private;
/* Begin Mutex: Lock to prevent concurrent update to Reference Count */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
ierr("Lock Mutex failed: %d\n", ret);
return ret;
}
/* Get next Reference Count */
use_count = priv->cref + 1;
DEBUGASSERT(use_count < UINT8_MAX && use_count > priv->cref);
if (use_count == 1)
{
/* If first user, power on the Touch Panel */
DEBUGASSERT(priv->board->set_power != NULL);
ret = priv->board->set_power(priv->board, true);
if (ret < 0)
{
goto out_lock;
}
/* Let Touch Panel power up before probing */
nxsig_usleep(100 * 1000);
/* Check that Touch Panel exists on I2C */
ret = gt9xx_probe_device(priv);
if (ret < 0)
{
/* No such device, power off the Touch Panel */
priv->board->set_power(priv->board, false);
goto out_lock;
}
/* Enable Touch Panel Interrupts */
DEBUGASSERT(priv->board->irq_enable);
priv->board->irq_enable(priv->board, true);
}
/* Set the Reference Count */
priv->cref = use_count;
/* End Mutex: Unlock to allow update to Reference Count */
out_lock:
nxmutex_unlock(&priv->devlock);
return ret;
}
/****************************************************************************
* Name: gt9xx_close
*
* Description:
* Close the Touch Panel Device. If this is the final close, we disable
* Touch Panel Interrupts and power off the Touch Panel.
*
* Input Parameters:
* filep - File Struct for Touch Panel
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_close(FAR struct file *filep)
{
FAR struct inode *inode;
FAR struct gt9xx_dev_s *priv;
int use_count;
int ret;
/* Get the Touch Panel Device */
iinfo("\n");
DEBUGASSERT(filep);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = inode->i_private;
/* Begin Mutex: Lock to prevent concurrent update to Reference Count */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
ierr("Lock Mutex failed: %d\n", ret);
return ret;
}
/* Decrement the Reference Count */
use_count = priv->cref - 1;
DEBUGASSERT(use_count >= 0);
if (use_count == 0)
{
/* If final user, disable Touch Panel Interrupts */
DEBUGASSERT(priv->board && priv->board->irq_enable);
priv->board->irq_enable(priv->board, false);
/* Power off the Touch Panel */
DEBUGASSERT(priv->board->set_power);
priv->board->set_power(priv->board, false);
}
/* Set the Reference Count */
priv->cref = use_count;
/* End Mutex: Unlock to allow update to Reference Count */
nxmutex_unlock(&priv->devlock);
return OK;
}
/****************************************************************************
* Name: gt9xx_poll
*
* Description:
* Setup or teardown a poll for the Touch Panel Device.
*
* Input Parameters:
* filep - File Struct for Touch Panel
* fds - The structure describing the events to be monitored, OR NULL if
* this is a request to stop monitoring events.
* setup - true: Setup the poll; false: Teardown the poll
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct gt9xx_dev_s *priv;
FAR struct inode *inode;
bool pending;
int ret = 0;
int i;
/* Get the Touch Panel Device */
iinfo("setup=%d\n", setup);
DEBUGASSERT(filep && fds);
inode = filep->f_inode;
DEBUGASSERT(inode && inode->i_private);
priv = (FAR struct gt9xx_dev_s *)inode->i_private;
/* Begin Mutex: Lock to prevent concurrent update to Poll Waiters */
ret = nxmutex_lock(&priv->devlock);
if (ret < 0)
{
ierr("Lock Mutex failed: %d\n", ret);
return ret;
}
if (setup)
{
/* If Poll Setup: Ignore waits that do not include POLLIN */
if ((fds->events & POLLIN) == 0)
{
ret = -EDEADLK;
goto out;
}
/* Find an available slot for the Poll Waiter */
for (i = 0; i < CONFIG_INPUT_GT9XX_NPOLLWAITERS; i++)
{
/* Found 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_INPUT_GT9XX_NPOLLWAITERS)
{
/* No slots available */
fds->priv = NULL;
ret = -EBUSY;
}
else
{
/* If Interrupt Pending is set, notify the Poll Waiters */
pending = priv->int_pending;
if (pending)
{
poll_notify(priv->fds,
CONFIG_INPUT_GT9XX_NPOLLWAITERS,
POLLIN);
}
}
}
else if (fds->priv)
{
/* If Poll Teardown: Remove the poll setup */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
DEBUGASSERT(slot != NULL);
*slot = NULL;
fds->priv = NULL;
}
/* End Mutex: Unlock to allow update to Poll Waiters */
out:
nxmutex_unlock(&priv->devlock);
return ret;
}
/****************************************************************************
* Name: gt9xx_isr_handler
*
* Description:
* Interrupt Handler for Touch Panel.
*
* Input Parameters:
* irq - IRQ Number
* context - IRQ Context
* arg - Touch Panel Device
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
static int gt9xx_isr_handler(int irq, FAR void *context, FAR void *arg)
{
FAR struct gt9xx_dev_s *priv = (FAR struct gt9xx_dev_s *)arg;
irqstate_t flags;
DEBUGASSERT(priv);
/* Begin Critical Section */
flags = enter_critical_section();
/* Set the Interrupt Pending Flag */
priv->int_pending = true;
/* End Critical Section */
leave_critical_section(flags);
/* Notify the Poll Waiters */
poll_notify(priv->fds, CONFIG_INPUT_GT9XX_NPOLLWAITERS, POLLIN);
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: gt9xx_register
*
* Description:
* Register the driver for Goodix GT9XX Touch Panel. Attach the
* Interrupt Handler for the Touch Panel and disable Touch Interrupts.
*
* Input Parameters:
* devpath - Device Path (e.g. "/dev/input0")
* dev - I2C Bus
* i2c_devaddr - I2C Address of Touch Panel
* board_config - Callback for Board-Specific Operations
*
* Returned Value:
* Zero (OK) on success; a negated errno value is returned on any failure.
*
****************************************************************************/
int gt9xx_register(FAR const char *devpath,
FAR struct i2c_master_s *i2c_dev,
uint8_t i2c_devaddr,
const struct gt9xx_board_s *board_config)
{
struct gt9xx_dev_s *priv;
int ret = 0;
iinfo("devpath=%s, i2c_devaddr=%d\n", devpath, i2c_devaddr);
DEBUGASSERT(devpath != NULL && i2c_dev != NULL && board_config != NULL);
/* Allocate the Touch Panel Device Structure */
priv = kmm_zalloc(sizeof(struct gt9xx_dev_s));
if (!priv)
{
ierr("GT9XX Memory Allocation failed\n");
return -ENOMEM;
}
/* Setup the Touch Panel Device Structure */
priv->addr = i2c_devaddr;
priv->i2c = i2c_dev;
priv->board = board_config;
nxmutex_init(&priv->devlock);
/* Register the Touch Input Driver */
ret = register_driver(devpath, &g_gt9xx_fileops, 0666, priv);
if (ret < 0)
{
nxmutex_destroy(&priv->devlock);
kmm_free(priv);
ierr("GT9XX Registration failed: %d\n", ret);
return ret;
}
/* Attach the Interrupt Handler */
DEBUGASSERT(priv->board->irq_attach);
priv->board->irq_attach(priv->board, gt9xx_isr_handler, priv);
/* Disable Touch Panel Interrupts */
DEBUGASSERT(priv->board->irq_enable);
priv->board->irq_enable(priv->board, false);
iinfo("GT9XX Touch Panel registered\n");
return OK;
}