1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-18 12:52:44 +01:00
RIOT/drivers/stmpe811/stmpe811.c
Gunar Schorcht b921bf460b drivers/stmpe811: init INT pin only if callback is defined
If the INT pin is initialized if the callback function parameter `cb` is `NULL`, the driver crashes the first time an interrupt is triggered. Therefore, the INT pin must be initialized only if also the callback function parameter `cb` is not `NULL`.

Even if the interrupt pin is not initialized here because the callback function parameter `cb` is NULL, the device interrupts are configured here so that they will work when the interrupt pin is initialized later, as is the case when the Touch Device API is used, for example. Since the interrupt state is cleared on each read, this is not a problem even if the interrupt pin is not used at all.
2023-08-13 17:11:54 +02:00

416 lines
11 KiB
C

/*
* Copyright (C) 2019 Inria
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup drivers_stmpe811
* @{
*
* @file
* @brief Device driver implementation for the STMPE811 touchscreen controller.
*
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
*
* @}
*/
#include <inttypes.h>
#include "ztimer.h"
#if IS_USED(MODULE_STMPE811_SPI)
#include "periph/spi.h"
#else
#include "periph/i2c.h"
#endif
#include "periph/gpio.h"
#include "stmpe811.h"
#include "stmpe811_constants.h"
#include "stmpe811_params.h"
#define ENABLE_DEBUG 0
#include "debug.h"
#if IS_USED(MODULE_STMPE811_SPI)
#define BUS (dev->params.spi)
#define CS (dev->params.cs)
#define CLK (dev->params.clk)
#define MODE (dev->params.mode)
#define WRITE_MASK (0x7F)
#else
#define BUS (dev->params.i2c)
#define ADDR (dev->params.addr)
#endif
#if IS_USED(MODULE_STMPE811_SPI) /* using SPI mode */
static inline void _acquire(const stmpe811_t *dev)
{
spi_acquire(BUS, CS, MODE, CLK);
}
static inline void _release(const stmpe811_t *dev)
{
spi_release(BUS);
}
static int _read_reg(const stmpe811_t *dev, uint8_t reg, uint8_t *data)
{
*data = spi_transfer_reg(BUS, CS, reg | 0x80, 0x00);
return 0;
}
static int _write_reg(const stmpe811_t *dev, uint8_t reg, uint8_t data)
{
(void)spi_transfer_reg(BUS, CS, (reg & WRITE_MASK), data);
return 0;
}
static int _read_burst(const stmpe811_t *dev, uint8_t reg, void *buf, size_t len)
{
uint8_t reg_read = reg | 0x80;
/* since SPI is in auto-increment mode subsequent reads will ignore the
content of the reg_read buffer, and itself can't overflow since it matches
the amount of data per register */
spi_transfer_regs(BUS, CS, reg_read, &reg_read, buf, len);
return 0;
}
#else /* using I2C mode */
static inline void _acquire(const stmpe811_t *dev)
{
i2c_acquire(BUS);
}
static inline void _release(const stmpe811_t *dev)
{
i2c_release(BUS);
}
static int _read_reg(const stmpe811_t *dev, uint8_t reg, uint8_t *data)
{
if (i2c_read_reg(BUS, ADDR, reg, data, 0) != 0) {
return -EIO;
}
return 0;
}
static int _write_reg(const stmpe811_t *dev, uint8_t reg, uint8_t data)
{
if (i2c_write_reg(BUS, ADDR, reg, data, 0) != 0) {
return -EIO;
}
return 0;
}
static int _read_burst(const stmpe811_t *dev, uint8_t reg, void *buf, size_t len)
{
if (i2c_read_regs(BUS, ADDR, reg, buf, len, 0) != 0) {
return -EIO;
}
return 0;
}
#endif /* bus mode selection */
static int _soft_reset(const stmpe811_t *dev)
{
if (_write_reg(dev, STMPE811_SYS_CTRL1, STMPE811_SYS_CTRL1_SOFT_RESET ) < 0) {
DEBUG("[stmpe811] soft reset: cannot write soft reset bit to SYS_CTRL1 register\n");
return -EPROTO;
}
ztimer_sleep(ZTIMER_MSEC, 10);
if (_write_reg(dev, STMPE811_SYS_CTRL1, 0) < 0) {
DEBUG("[stmpe811] soft reset: cannot clear SYS_CTRL1 register\n");
return -EPROTO;
}
ztimer_sleep(ZTIMER_MSEC, 2);
return 0;
}
static void _reset_fifo(const stmpe811_t *dev)
{
_write_reg(dev, STMPE811_FIFO_CTRL_STA, STMPE811_FIFO_CTRL_STA_RESET);
_write_reg(dev, STMPE811_FIFO_CTRL_STA, 0);
}
static void _clear_interrupt_status(const stmpe811_t *dev)
{
_write_reg(dev, STMPE811_INT_STA, 0xff);
}
#if IS_USED(MODULE_STMPE811_SPI)
static int _stmpe811_check_mode(stmpe811_t *dev)
{
/* can iterate directly through the enum since they might not be
monotonically incrementing */
uint8_t modes[] = { SPI_MODE_0, SPI_MODE_1, SPI_MODE_2, SPI_MODE_3};
uint8_t reg;
for (uint8_t i = 0; i < sizeof(modes); i++) {
DEBUG("[stmpe811] init: set spi mode to 0x%02x ... ", modes[i]);
dev->params.mode = modes[i];
/* acquire */
_acquire(dev);
/* configure auto increment SPI */
_read_reg(dev, STMPE811_SPI_CFG, &reg);
reg = STMPE811_SPI_CFG_AUTO_INCR;
_write_reg(dev, STMPE811_SPI_CFG, reg);
_read_reg(dev, STMPE811_SPI_CFG, &reg);
if (reg & STMPE811_SPI_CFG_AUTO_INCR) {
DEBUG("success\n");
_release(dev);
return 0;
}
DEBUG("failed\n");
_release(dev);
}
return 1;
}
#endif
int stmpe811_init(stmpe811_t *dev, const stmpe811_params_t *params, stmpe811_event_cb_t cb,
void *arg)
{
dev->params = *params;
dev->prev_x = 0;
dev->prev_y = 0;
int ret = 0;
uint8_t reg;
#if IS_USED(MODULE_STMPE811_SPI)
/* configure the chip-select pin */
if (spi_init_cs(BUS, CS) != SPI_OK) {
DEBUG("[stmpe811] error: unable to configure chip the select pin\n");
return -EIO;
}
/* check mode configuration */
if(_stmpe811_check_mode(dev) != 0) {
DEBUG("[stmpe811] error: couldn't setup SPI\n");
return -EIO;
}
#endif
/* acquire bus */
_acquire(dev);
uint16_t device_id;
if (_read_burst(dev, STMPE811_CHIP_ID, &device_id, 2) < 0) {
DEBUG("[stmpe811] init: cannot read CHIP_ID register\n");
_release(dev);
return -EPROTO;
}
device_id = (device_id << 8) | (device_id >> 8);
if (device_id != STMPE811_CHIP_ID_VALUE) {
DEBUG("[stmpe811] init: invalid device id (actual: 0x%04X, expected: 0x%04X)\n",
device_id, STMPE811_CHIP_ID_VALUE);
_release(dev);
return -ENODEV;
}
DEBUG("[stmpe811] init: valid device\n");
ret = _soft_reset(dev);
if (ret != 0) {
DEBUG("[stmpe811] init: reset failed\n");
_release(dev);
return -EIO;
}
DEBUG("[stmpe811] init: soft reset done\n");
/* Initialization sequence */
/* disable temperature sensor and GPIO */
ret =
_write_reg(dev, STMPE811_SYS_CTRL2,
(STMPE811_SYS_CTRL2_TS_OFF | STMPE811_SYS_CTRL2_GPIO_OFF));
/* set to 80 cycles and adc resolution to 12 bit*/
reg =
((uint8_t)(STMPE811_ADC_CTRL1_SAMPLE_TIME_80 << STMPE811_ADC_CTRL1_SAMPLE_TIME_POS) |
STMPE811_ADC_CTRL1_MOD_12B);
ret += _write_reg(dev, STMPE811_ADC_CTRL1, reg);
/* set adc clock speed to 3.25 MHz */
ret += _write_reg(dev, STMPE811_ADC_CTRL2, STMPE811_ADC_CTRL2_FREQ_3_25MHZ);
/* set GPIO AF to function as ts/adc */
ret += _write_reg(dev, STMPE811_GPIO_ALT_FUNCTION, 0x00);
/* set touchscreen configuration */
reg = ((uint8_t)(STMPE811_TSC_CFG_AVE_CTRL_4 << STMPE811_TSC_CFG_AVE_CTRL_POS) |
(uint8_t)(STMPE811_TSC_CFG_TOUCH_DET_DELAY_500US <<
STMPE811_TSC_CFG_TOUCH_DET_DELAY_POS) |
(STMPE811_TSC_CFG_SETTLING_500US));
ret += _write_reg(dev, STMPE811_TSC_CFG, reg);
/* set fifo threshold */
ret += _write_reg(dev, STMPE811_FIFO_TH, 0x01);
/* reset fifo */
_reset_fifo(dev);
/* set fractional part to 7, whole part to 1 */
ret += _write_reg(dev, STMPE811_TSC_FRACTION_Z, STMPE811_TSC_FRACTION_Z_7_1);
/* set current limit value to 50 mA */
ret += _write_reg(dev, STMPE811_TSC_I_DRIVE, STMPE811_TSC_I_DRIVE_50MA);
/* enable touchscreen clock */
ret += _read_reg(dev, STMPE811_SYS_CTRL2, &reg);
reg &= ~STMPE811_SYS_CTRL2_TSC_OFF;
ret += _write_reg(dev, STMPE811_SYS_CTRL2, reg);
ret += _read_reg(dev, STMPE811_TSC_CTRL, &reg);
reg |= STMPE811_TSC_CTRL_EN;
ret += _write_reg(dev, STMPE811_TSC_CTRL, reg);
/* clear interrupt status */
_clear_interrupt_status(dev);
if (gpio_is_valid(dev->params.int_pin)) {
DEBUG("[stmpe811] init: configuring touchscreen interrupt\n");
if (cb) {
gpio_init_int(dev->params.int_pin, GPIO_IN, GPIO_FALLING, cb, arg);
}
/* Enable touchscreen interrupt */
ret += _write_reg(dev, STMPE811_INT_EN, STMPE811_INT_EN_TOUCH_DET);
/* Enable global interrupt */
ret += _write_reg(dev, STMPE811_INT_CTRL,
STMPE811_INT_CTRL_GLOBAL_INT | STMPE811_INT_CTRL_INT_TYPE);
}
if (ret < 0) {
_release(dev);
DEBUG("[stmpe811] init: initialization sequence failed\n");
return -EPROTO;
}
/* Release I2C device */
_release(dev);
DEBUG("[stmpe811] initialization successful\n");
return ret;
}
int stmpe811_read_touch_position(stmpe811_t *dev, stmpe811_touch_position_t *position)
{
uint16_t tmp_x, tmp_y;
/* Acquire device bus */
_acquire(dev);
/* Ensure there's a least one position measured in the FIFO */
uint8_t fifo_size = 0;
do {
_read_reg(dev, STMPE811_FIFO_SIZE, &fifo_size);
} while (!fifo_size);
uint8_t xyz[4];
uint32_t xyz_ul;
#if IS_USED(MODULE_STMPE811_SPI)
for (uint8_t i = 0; i < sizeof(xyz); i++) {
if (_read_reg(dev, STMPE811_TSC_DATA_NON_INC, &xyz[i]) < 0) {
DEBUG("[stmpe811] position: cannot read position\n");
_release(dev);
return -EPROTO;
}
}
#else
if (_read_burst(dev, STMPE811_TSC_DATA_NON_INC, xyz, sizeof(xyz)) < 0) {
DEBUG("[stmpe811] position: cannot read position\n");
_release(dev);
return -EPROTO;
}
#endif
/* Release device bus */
_release(dev);
xyz_ul = ((uint32_t)xyz[0] << 24) | ((uint32_t)xyz[1] << 16) | \
((uint32_t)xyz[2] << 8) | (xyz[3] << 0);
tmp_x = (xyz_ul >> 20) & 0xfff;
tmp_y = (xyz_ul >> 8) & 0xfff;
/* Y value first correction */
tmp_y -= 360;
/* Y value second correction */
tmp_y /= 11;
/* clamp y position */
if (tmp_y > dev->params.ymax) {
tmp_y = dev->prev_y;
}
/* X value first correction */
if (tmp_x <= 3000) {
tmp_x = 3870 - tmp_x;
}
else {
tmp_x = 3800 - tmp_x;
}
/* X value second correction */
tmp_x /= 15;
/* clamp x position */
if (tmp_x > dev->params.xmax) {
tmp_x = dev->prev_x;
}
dev->prev_x = tmp_x;
dev->prev_y = tmp_y;
position->x = tmp_x;
position->y = tmp_y;
return 0;
}
int stmpe811_read_touch_state(const stmpe811_t *dev, stmpe811_touch_state_t *state)
{
uint8_t val;
/* Acquire device bus */
_acquire(dev);
if (_read_reg(dev, STMPE811_TSC_CTRL, &val) < 0) {
DEBUG("[stmpe811] position: cannot read touch state\n");
_release(dev);
return -EPROTO;
}
_clear_interrupt_status(dev);
if ((val & STMPE811_TSC_CTRL_STA)) {
*state = STMPE811_TOUCH_STATE_PRESSED;
}
else {
_reset_fifo(dev);
*state = STMPE811_TOUCH_STATE_RELEASED;
}
/* Release I2C device */
_release(dev);
return 0;
}