crides
3 years ago
committed by
Julia Luna
8 changed files with 437 additions and 2 deletions
@ -0,0 +1,8 @@ |
|||||||
|
# Copyright (c) 2022 The ZMK Contributors |
||||||
|
# SPDX-License-Identifier: MIT |
||||||
|
|
||||||
|
zephyr_include_directories(.) |
||||||
|
|
||||||
|
zephyr_library() |
||||||
|
|
||||||
|
zephyr_library_sources(cirque_trackpad.c) |
@ -0,0 +1,51 @@ |
|||||||
|
# Copyright (c) 2022 The ZMK Contributors |
||||||
|
# SPDX-License-Identifier: MIT |
||||||
|
|
||||||
|
menuconfig PINNACLE |
||||||
|
bool "PINNACLE Incremental Encoder Sensor" |
||||||
|
depends on GPIO |
||||||
|
depends on SPI |
||||||
|
help |
||||||
|
Enable driver for Cirque Pinnacle trackpads |
||||||
|
|
||||||
|
if PINNACLE |
||||||
|
|
||||||
|
choice |
||||||
|
prompt "Trigger mode" |
||||||
|
default PINNACLE_TRIGGER_NONE |
||||||
|
help |
||||||
|
Specify the type of triggering to be used by the driver. |
||||||
|
|
||||||
|
config PINNACLE_TRIGGER_NONE |
||||||
|
bool "No trigger" |
||||||
|
|
||||||
|
config PINNACLE_TRIGGER_GLOBAL_THREAD |
||||||
|
bool "Use global thread" |
||||||
|
depends on GPIO |
||||||
|
select PINNACLE_TRIGGER |
||||||
|
|
||||||
|
config PINNACLE_TRIGGER_OWN_THREAD |
||||||
|
bool "Use own thread" |
||||||
|
depends on GPIO |
||||||
|
select PINNACLE_TRIGGER |
||||||
|
|
||||||
|
endchoice |
||||||
|
|
||||||
|
config PINNACLE_TRIGGER |
||||||
|
bool |
||||||
|
|
||||||
|
config PINNACLE_THREAD_PRIORITY |
||||||
|
int "Thread priority" |
||||||
|
depends on PINNACLE_TRIGGER_OWN_THREAD |
||||||
|
default 10 |
||||||
|
help |
||||||
|
Priority of thread used by the driver to handle interrupts. |
||||||
|
|
||||||
|
config PINNACLE_THREAD_STACK_SIZE |
||||||
|
int "Thread stack size" |
||||||
|
depends on PINNACLE_TRIGGER_OWN_THREAD |
||||||
|
default 1024 |
||||||
|
help |
||||||
|
Stack size of thread used by the driver to handle interrupts. |
||||||
|
|
||||||
|
endif # PINNACLE |
@ -0,0 +1,260 @@ |
|||||||
|
#define DT_DRV_COMPAT cirque_pinnacle |
||||||
|
|
||||||
|
#include <drivers/spi.h> |
||||||
|
#include <init.h> |
||||||
|
#include <drivers/sensor.h> |
||||||
|
#include <zmk/sensors.h> |
||||||
|
#include <logging/log.h> |
||||||
|
|
||||||
|
#include "cirque_trackpad.h" |
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(pinnacle, CONFIG_SENSOR_LOG_LEVEL); |
||||||
|
|
||||||
|
static int pinnacle_seq_read(const struct device *dev, const uint8_t start, uint8_t *buf, const uint8_t len) { |
||||||
|
uint8_t tx_buffer[len + 3], rx_dummy[3]; |
||||||
|
tx_buffer[0] = PINNACLE_READ | start; |
||||||
|
memset(&tx_buffer[1], PINNACLE_AUTOINC, len + 1); |
||||||
|
tx_buffer[len + 2] = PINNACLE_DUMMY; |
||||||
|
|
||||||
|
const struct spi_buf tx_buf = { |
||||||
|
.buf = tx_buffer, |
||||||
|
.len = len + 3, |
||||||
|
}; |
||||||
|
const struct spi_buf_set tx = { |
||||||
|
.buffers = &tx_buf, |
||||||
|
.count = 1, |
||||||
|
}; |
||||||
|
struct spi_buf rx_buf[2] = { |
||||||
|
{ |
||||||
|
.buf = rx_dummy, |
||||||
|
.len = 3, |
||||||
|
}, |
||||||
|
{ |
||||||
|
.buf = buf, |
||||||
|
.len = len, |
||||||
|
}, |
||||||
|
}; |
||||||
|
const struct spi_buf_set rx = { |
||||||
|
.buffers = rx_buf, |
||||||
|
.count = 2, |
||||||
|
}; |
||||||
|
const struct pinnacle_data *data = dev->data; |
||||||
|
const struct pinnacle_config *config = dev->config; |
||||||
|
return spi_transceive(data->spi, &config->spi_config, &tx, &rx); |
||||||
|
} |
||||||
|
|
||||||
|
static int pinnacle_write(const struct device *dev, const uint8_t addr, const uint8_t val) { |
||||||
|
uint8_t tx_buffer[2] = { PINNACLE_WRITE | addr, val }; |
||||||
|
uint8_t rx_buffer[2]; |
||||||
|
|
||||||
|
const struct spi_buf tx_buf = { |
||||||
|
.buf = tx_buffer, |
||||||
|
.len = 2, |
||||||
|
}; |
||||||
|
const struct spi_buf_set tx = { |
||||||
|
.buffers = &tx_buf, |
||||||
|
.count = 1, |
||||||
|
}; |
||||||
|
const struct spi_buf rx_buf[1] = { |
||||||
|
{ |
||||||
|
.buf = rx_buffer, |
||||||
|
.len = sizeof(rx_buffer), |
||||||
|
}, |
||||||
|
}; |
||||||
|
const struct spi_buf_set rx = { |
||||||
|
.buffers = rx_buf, |
||||||
|
.count = 1, |
||||||
|
}; |
||||||
|
const struct pinnacle_data *data = dev->data; |
||||||
|
const struct pinnacle_config *config = dev->config; |
||||||
|
const int ret = spi_transceive(data->spi, &config->spi_config, &tx, &rx); |
||||||
|
if (rx_buffer[1] != 0xFB) { |
||||||
|
LOG_ERR("bad ret val"); |
||||||
|
return -EIO; |
||||||
|
} |
||||||
|
if (ret < 0) { |
||||||
|
LOG_ERR("spi ret: %d", ret); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
||||||
|
|
||||||
|
static int pinnacle_channel_get(const struct device *dev, enum sensor_channel chan, struct sensor_value *val) { |
||||||
|
const struct pinnacle_data *data = dev->data; |
||||||
|
switch (chan) { |
||||||
|
case SENSOR_CHAN_POS_DX: val->val1 = data->dx; break; |
||||||
|
case SENSOR_CHAN_POS_DY: val->val1 = data->dy; break; |
||||||
|
case SENSOR_CHAN_PRESS: val->val1 = data->btn; break; |
||||||
|
default: return -ENOTSUP; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int pinnacle_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr, const struct sensor_value *val) { |
||||||
|
const struct pinnacle_config *config = dev->config; |
||||||
|
if (attr == SENSOR_ATTR_PINNACLE_GE) { |
||||||
|
const uint8_t ge_set = val->val1 ? 0 : PINNACLE_FEED_CFG2_DIS_GE; |
||||||
|
const uint8_t taps_set = config->no_taps ? PINNACLE_FEED_CFG2_DIS_TAP : 0; |
||||||
|
pinnacle_write(dev, PINNACLE_FEED_CFG2, ge_set | taps_set); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return -ENOTSUP; |
||||||
|
} |
||||||
|
|
||||||
|
static int pinnacle_sample_fetch(const struct device *dev, enum sensor_channel chan) { |
||||||
|
uint8_t packet[3]; |
||||||
|
int res = pinnacle_seq_read(dev, PINNACLE_2_2_PACKET0, packet, 3); |
||||||
|
if (res < 0) { |
||||||
|
LOG_ERR("res: %d", res); |
||||||
|
return res; |
||||||
|
} |
||||||
|
struct pinnacle_data *data = dev->data; |
||||||
|
data->btn = packet[0] & PINNACLE_PACKET0_BTN_PRIM; |
||||||
|
data->dx = (int16_t) (int8_t) packet[1]; |
||||||
|
data->dy = (int16_t) (int8_t) packet[2]; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER |
||||||
|
static void set_int(const struct device *dev, const bool en) { |
||||||
|
const struct pinnacle_config *config = dev->config; |
||||||
|
int ret = gpio_pin_interrupt_configure(config->dr_port, config->dr_pin, en ? GPIO_INT_LEVEL_ACTIVE : GPIO_INT_DISABLE); |
||||||
|
if (ret < 0) { |
||||||
|
LOG_ERR("can't set interrupt"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static int pinnacle_trigger_set(const struct device *dev, const struct sensor_trigger *trig, sensor_trigger_handler_t handler) { |
||||||
|
struct pinnacle_data *data = dev->data; |
||||||
|
|
||||||
|
set_int(dev, false); |
||||||
|
if (trig->type != SENSOR_TRIG_DATA_READY) { |
||||||
|
return -ENOTSUP; |
||||||
|
} |
||||||
|
data->data_ready_trigger = trig; |
||||||
|
data->data_ready_handler = handler; |
||||||
|
set_int(dev, true); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static void pinnacle_int_cb(const struct device *dev) { |
||||||
|
struct pinnacle_data *data = dev->data; |
||||||
|
data->data_ready_handler(dev, data->data_ready_trigger); |
||||||
|
set_int(dev, true); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER_OWN_THREAD |
||||||
|
static void pinnacle_thread(void *arg) { |
||||||
|
const struct device *dev = arg; |
||||||
|
struct pinnacle_data *data = dev->data; |
||||||
|
|
||||||
|
while (1) { |
||||||
|
k_sem_take(&data->gpio_sem, K_FOREVER); |
||||||
|
pinnacle_int_cb(dev); |
||||||
|
pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear SW_DR
|
||||||
|
} |
||||||
|
} |
||||||
|
#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) |
||||||
|
static void pinnacle_work_cb(struct k_work *work) { |
||||||
|
struct pinnacle_data *data = CONTAINER_OF(work, struct pinnacle_data, work); |
||||||
|
pinnacle_int_cb(data->dev); |
||||||
|
pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear SW_DR
|
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
static void pinnacle_gpio_cb(const struct device *port, struct gpio_callback *cb, uint32_t pins) { |
||||||
|
struct pinnacle_data *data = CONTAINER_OF(cb, struct pinnacle_data, gpio_cb); |
||||||
|
const struct device *dev = data->dev; |
||||||
|
#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) |
||||||
|
k_sem_give(&data->gpio_sem); |
||||||
|
#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) |
||||||
|
k_work_submit(&data->work); |
||||||
|
#endif |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
#define SPI_BUS DT_BUS(DT_DRV_INST(0)) |
||||||
|
#define SPI_REG DT_REG_ADDR(DT_DRV_INST(0)) |
||||||
|
|
||||||
|
static int pinnacle_init(const struct device *dev) { |
||||||
|
struct pinnacle_data *data = dev->data; |
||||||
|
const struct pinnacle_config *config = dev->config; |
||||||
|
data->spi = DEVICE_DT_GET(SPI_BUS); |
||||||
|
|
||||||
|
pinnacle_write(dev, PINNACLE_STATUS1, 0); // Clear CC
|
||||||
|
pinnacle_write(dev, PINNACLE_Z_IDLE, 0); // No Z-Idle packets
|
||||||
|
if (config->sleep_en) { |
||||||
|
pinnacle_write(dev, PINNACLE_SYS_CFG, PINNACLE_SYS_CFG_EN_SLEEP); |
||||||
|
} |
||||||
|
if (config->no_taps) { |
||||||
|
pinnacle_write(dev, PINNACLE_FEED_CFG2, PINNACLE_FEED_CFG2_DIS_TAP); |
||||||
|
} |
||||||
|
uint8_t feed_cfg1 = PINNACLE_FEED_CFG1_EN_FEED; |
||||||
|
if (config->invert_x) { |
||||||
|
feed_cfg1 |= PINNACLE_FEED_CFG1_INV_X; |
||||||
|
} |
||||||
|
if (config->invert_y) { |
||||||
|
feed_cfg1 |= PINNACLE_FEED_CFG1_INV_Y; |
||||||
|
} |
||||||
|
if (feed_cfg1) { |
||||||
|
pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER |
||||||
|
data->dev = dev; |
||||||
|
gpio_pin_configure(config->dr_port, config->dr_pin, GPIO_INPUT | config->dr_flags); |
||||||
|
gpio_init_callback(&data->gpio_cb, pinnacle_gpio_cb, BIT(config->dr_pin)); |
||||||
|
int ret = gpio_add_callback(config->dr_port, &data->gpio_cb); |
||||||
|
if (ret < 0) { |
||||||
|
LOG_ERR("Failed to set DR callback: %d", ret); |
||||||
|
return -EIO; |
||||||
|
} |
||||||
|
|
||||||
|
#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) |
||||||
|
k_sem_init(&data->gpio_sem, 0, UINT_MAX); |
||||||
|
|
||||||
|
k_thread_create(&data->thread, data->thread_stack, CONFIG_PINNACLE_THREAD_STACK_SIZE, |
||||||
|
(k_thread_entry_t) pinnacle_thread, (void *) dev, 0, NULL, |
||||||
|
K_PRIO_COOP(CONFIG_PINNACLE_THREAD_PRIORITY), 0, K_NO_WAIT); |
||||||
|
#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) |
||||||
|
k_work_init(&data->work, pinnacle_work_cb); |
||||||
|
#endif |
||||||
|
pinnacle_write(dev, PINNACLE_FEED_CFG1, feed_cfg1); |
||||||
|
#endif |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static const struct sensor_driver_api pinnacle_driver_api = { |
||||||
|
#if CONFIG_PINNACLE_TRIGGER |
||||||
|
.trigger_set = pinnacle_trigger_set, |
||||||
|
#endif |
||||||
|
.sample_fetch = pinnacle_sample_fetch, |
||||||
|
.channel_get = pinnacle_channel_get, |
||||||
|
.attr_set = pinnacle_attr_set, |
||||||
|
}; |
||||||
|
|
||||||
|
static struct pinnacle_data pinnacle_data; |
||||||
|
static const struct pinnacle_config pinnacle_config = { |
||||||
|
.spi_cs = { |
||||||
|
.gpio_dev = DEVICE_DT_GET(DT_GPIO_CTLR_BY_IDX(SPI_BUS, cs_gpios, SPI_REG)), |
||||||
|
.gpio_pin = DT_GPIO_PIN_BY_IDX(SPI_BUS, cs_gpios, SPI_REG), |
||||||
|
.delay = 0, |
||||||
|
.gpio_dt_flags = DT_GPIO_FLAGS_BY_IDX(SPI_BUS, cs_gpios, SPI_REG), |
||||||
|
}, |
||||||
|
.spi_config = { |
||||||
|
.cs = &pinnacle_config.spi_cs, |
||||||
|
.frequency = DT_INST_PROP(0, spi_max_frequency), |
||||||
|
.slave = DT_INST_REG_ADDR(0), |
||||||
|
.operation = (SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_LINES_SINGLE | SPI_TRANSFER_MSB), |
||||||
|
}, |
||||||
|
.invert_x = DT_INST_PROP(0, invert_x), |
||||||
|
.invert_y = DT_INST_PROP(0, invert_y), |
||||||
|
.sleep_en = DT_INST_PROP(0, sleep), |
||||||
|
.no_taps = DT_INST_PROP(0, no_taps), |
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER |
||||||
|
.dr_port = DEVICE_DT_GET(DT_GPIO_CTLR(DT_DRV_INST(0), dr_gpios)), |
||||||
|
.dr_pin = DT_INST_GPIO_PIN(0, dr_gpios), |
||||||
|
.dr_flags = DT_INST_GPIO_FLAGS(0, dr_gpios), |
||||||
|
#endif |
||||||
|
}; |
||||||
|
|
||||||
|
DEVICE_DT_INST_DEFINE(0, pinnacle_init, device_pm_control_nop, &pinnacle_data, &pinnacle_config, POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &pinnacle_driver_api); |
@ -0,0 +1,80 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <device.h> |
||||||
|
|
||||||
|
#define PINNACLE_READ 0xA0 |
||||||
|
#define PINNACLE_WRITE 0x80 |
||||||
|
|
||||||
|
#define PINNACLE_AUTOINC 0xFC |
||||||
|
#define PINNACLE_DUMMY 0xFB |
||||||
|
|
||||||
|
// Registers
|
||||||
|
#define PINNACLE_FW_ID 0x00 // ASIC ID.
|
||||||
|
#define PINNACLE_FW_VER 0x01 // Firmware Version Firmware revision number.
|
||||||
|
#define PINNACLE_STATUS1 0x02 // Contains status flags about the state of Pinnacle.
|
||||||
|
#define PINNACLE_SYS_CFG 0x03 // Contains system operation and configuration bits.
|
||||||
|
#define PINNACLE_SYS_CFG_EN_SLEEP BIT(2) |
||||||
|
#define PINNACLE_SYS_CFG_SHUTDOWN BIT(1) |
||||||
|
#define PINNACLE_SYS_CFG_RESET BIT(0) |
||||||
|
|
||||||
|
#define PINNACLE_FEED_CFG1 0x04 // Contains feed operation and configuration bits.
|
||||||
|
#define PINNACLE_FEED_CFG1_EN_FEED BIT(0) |
||||||
|
#define PINNACLE_FEED_CFG1_ABS_MODE BIT(1) |
||||||
|
#define PINNACLE_FEED_CFG1_DIS_FILT BIT(2) |
||||||
|
#define PINNACLE_FEED_CFG1_DIS_X BIT(3) |
||||||
|
#define PINNACLE_FEED_CFG1_DIS_Y BIT(4) |
||||||
|
#define PINNACLE_FEED_CFG1_INV_X BIT(6) |
||||||
|
#define PINNACLE_FEED_CFG1_INV_Y BIT(7) |
||||||
|
#define PINNACLE_FEED_CFG2 0x05 // Contains feed operation and configuration bits.
|
||||||
|
#define PINNACLE_FEED_CFG2_EN_IM BIT(0) // Intellimouse
|
||||||
|
#define PINNACLE_FEED_CFG2_DIS_TAP BIT(1) // Disable all taps
|
||||||
|
#define PINNACLE_FEED_CFG2_DIS_SEC BIT(2) // Disable secondary tap
|
||||||
|
#define PINNACLE_FEED_CFG2_DIS_SCRL BIT(3) // Disable scroll
|
||||||
|
#define PINNACLE_FEED_CFG2_DIS_GE BIT(4) // Disable GlideExtend
|
||||||
|
#define PINNACLE_FEED_CFG2_SWAP_XY BIT(7) // Swap X & Y
|
||||||
|
#define PINNACLE_CAL_CFG 0x07 // Contains calibration configuration bits.
|
||||||
|
#define PINNACLE_PS2_AUX 0x08 // Contains Data register for PS/2 Aux Control.
|
||||||
|
#define PINNACLE_SAMPLE 0x09 // Sample Rate Number of samples generated per second.
|
||||||
|
#define PINNACLE_Z_IDLE 0x0A // Number of Z=0 packets sent when Z goes from >0 to 0.
|
||||||
|
#define PINNACLE_Z_SCALER 0x0B // Contains the pen Z_On threshold.
|
||||||
|
#define PINNACLE_SLEEP_INTERVAL 0x0C // Sleep Interval
|
||||||
|
#define PINNACLE_SLEEP_TIMER 0x0D // Sleep Timer
|
||||||
|
#define PINNACLE_AG_PACKET0 0x10 // trackpad Data (Pinnacle AG)
|
||||||
|
#define PINNACLE_2_2_PACKET0 0x12 // trackpad Data
|
||||||
|
#define PINNACLE_REG_COUNT 0x18 |
||||||
|
|
||||||
|
#define PINNACLE_PACKET0_BTN_PRIM BIT(0) // Primary button
|
||||||
|
#define PINNACLE_PACKET0_BTN_SEC BIT(1) // Secondary button
|
||||||
|
#define PINNACLE_PACKET0_BTN_AUX BIT(2) // Auxiliary (middle?) button
|
||||||
|
#define PINNACLE_PACKET0_X_SIGN BIT(4) // X delta sign
|
||||||
|
#define PINNACLE_PACKET0_Y_SIGN BIT(5) // Y delta sign
|
||||||
|
|
||||||
|
struct pinnacle_data { |
||||||
|
const struct device *spi; |
||||||
|
int16_t dx, dy; |
||||||
|
int8_t wheel; |
||||||
|
uint8_t btn; |
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER |
||||||
|
const struct device *dev; |
||||||
|
const struct sensor_trigger *data_ready_trigger; |
||||||
|
struct gpio_callback gpio_cb; |
||||||
|
sensor_trigger_handler_t data_ready_handler; |
||||||
|
#if defined(CONFIG_PINNACLE_TRIGGER_OWN_THREAD) |
||||||
|
K_THREAD_STACK_MEMBER(thread_stack, CONFIG_PINNACLE_THREAD_STACK_SIZE); |
||||||
|
struct k_sem gpio_sem; |
||||||
|
struct k_thread thread; |
||||||
|
#elif defined(CONFIG_PINNACLE_TRIGGER_GLOBAL_THREAD) |
||||||
|
struct k_work work; |
||||||
|
#endif |
||||||
|
#endif |
||||||
|
}; |
||||||
|
|
||||||
|
struct pinnacle_config { |
||||||
|
struct spi_cs_control spi_cs; |
||||||
|
struct spi_config spi_config; |
||||||
|
bool invert_x, invert_y, sleep_en, no_taps; |
||||||
|
#ifdef CONFIG_PINNACLE_TRIGGER |
||||||
|
const struct device *dr_port; |
||||||
|
uint8_t dr_pin, dr_flags; |
||||||
|
#endif |
||||||
|
}; |
@ -0,0 +1,27 @@ |
|||||||
|
description: | |
||||||
|
Sensor driver for the Cirque Pinnacle trackpad ASICs |
||||||
|
|
||||||
|
compatible: "cirque,pinnacle" |
||||||
|
|
||||||
|
include: base.yaml |
||||||
|
|
||||||
|
on-bus: spi |
||||||
|
|
||||||
|
properties: |
||||||
|
spi-max-frequency: |
||||||
|
type: int |
||||||
|
required: true |
||||||
|
description: | |
||||||
|
Maximum SPI clock speed supported by the device, in Hz. |
||||||
|
dr-gpios: |
||||||
|
type: phandle-array |
||||||
|
required: true |
||||||
|
description: Data ready pin for the trackpad |
||||||
|
invert-x: |
||||||
|
type: boolean |
||||||
|
invert-y: |
||||||
|
type: boolean |
||||||
|
sleep: |
||||||
|
type: boolean |
||||||
|
no-taps: |
||||||
|
type: boolean |
Loading…
Reference in new issue