nuttx/drivers/input/gt9xx.c

952 lines
24 KiB
C
Raw Normal View History

/****************************************************************************
* 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 */
FAR struct i2c_master_s *i2c;
uint8_t addr;
/* Callback for Board-Specific Operations */
FAR 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 */
FAR 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 */
inode = filep->f_inode;
DEBUGASSERT(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");
inode = filep->f_inode;
DEBUGASSERT(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");
inode = filep->f_inode;
DEBUGASSERT(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(fds);
inode = filep->f_inode;
DEBUGASSERT(inode->i_private);
priv = 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(&fds, 1, 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;
}