Files
tactility/Drivers/drv2605-module/source/drv2605.cpp
Ken Van Hoeylandt d896657bf9 Device migrations, drivers and fixes (#571)
Device migrations:

- cyd-4848S040c
- guition-jc1060p470ciwy
- guition-jc2432w328c
- guition-jc3248w535c (known issue with display and touch, Shadowtrance will look into it)
- guition-jc8048w550c
- heltec-wifi-lora-32-v3
- lilygo-tdeck-max (excluding graphics)
- lilygo-tdisplay
- lilygo-tdisplay-s3
- lilygo-tdongle-s3
- lilygo-tlora-pager

Driver migrations:

- Implemented haptic driver interface in kernel
- AXS15231b
- BQ25896
- BQ27220
- CST328
- CST6xx
- DRV2605
- JD9165
- Custom LilyGO driver for T-Lora Pager
- SSD1306
- ST7701
- ST7735
- SY6970
- TCA8418

Fixes/improvements:

- Boot app: support for multiple power devices, improved UI
- lvgl_devices and related code: fixes for mapping
- Support for arrays in dts parser
2026-07-18 15:01:42 +02:00

152 lines
5.8 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include <drivers/drv2605.h>
#include <drv2605_module.h>
#include <tactility/device.h>
#include <tactility/driver.h>
#include <tactility/drivers/haptic.h>
#include <tactility/drivers/i2c_controller.h>
#include <tactility/error.h>
#include <tactility/log.h>
#define TAG "DRV2605"
#define GET_CONFIG(device) (static_cast<const Drv2605Config*>((device)->config))
static const TickType_t I2C_TIMEOUT = pdMS_TO_TICKS(50);
// DRV260x register map (ported from the deprecated HAL's Drv2605.h/.cpp).
static constexpr uint8_t REG_STATUS = 0x00;
static constexpr uint8_t REG_MODE = 0x01;
static constexpr uint8_t REG_REALTIME_PLAYBACK_INPUT = 0x02;
static constexpr uint8_t REG_WAVE_LIBRARY_SELECT = 0x03;
static constexpr uint8_t REG_WAVE_SEQUENCE_1 = 0x04; // WaveSequence1..8 = 0x04..0x0B
static constexpr uint8_t REG_GO = 0x0C;
static constexpr uint8_t REG_OVERDRIVE_TIME_OFFSET = 0x0D;
static constexpr uint8_t REG_SUSTAIN_TIME_OFFSET_POSITIVE = 0x0E;
static constexpr uint8_t REG_SUSTAIN_TIME_OFFSET_NEGATIVE = 0x0F;
static constexpr uint8_t REG_BRAKE_TIME_OFFSET = 0x10;
static constexpr uint8_t REG_AUDIO_INPUT_LEVEL_MAX = 0x13;
static constexpr uint8_t REG_FEEDBACK = 0x1A;
static constexpr uint8_t REG_CONTROL3 = 0x1D;
static constexpr uint8_t FEEDBACK_N_ERM_LRA = 1u << 7;
static constexpr uint8_t CONTROL3_ERM_OPEN_LOOP = 1u << 5;
// 3x strong-click "buzz", matching the deprecated HAL's Drv2605::setWaveFormForBuzz().
static constexpr uint8_t DEFAULT_STARTUP_WAVEFORM[] = { 1, 1, 1, 0 };
extern "C" {
// region HapticApi
static error_t set_waveform(Device* device, uint8_t slot, uint8_t waveform) {
const auto* config = GET_CONFIG(device);
auto* i2c = device_get_parent(device);
return i2c_controller_register8_set(i2c, config->address, static_cast<uint8_t>(REG_WAVE_SEQUENCE_1 + slot), waveform, I2C_TIMEOUT);
}
static error_t select_library(Device* device, uint8_t library) {
const auto* config = GET_CONFIG(device);
auto* i2c = device_get_parent(device);
return i2c_controller_register8_set(i2c, config->address, REG_WAVE_LIBRARY_SELECT, library, I2C_TIMEOUT);
}
static error_t start_playback(Device* device) {
const auto* config = GET_CONFIG(device);
auto* i2c = device_get_parent(device);
return i2c_controller_register8_set(i2c, config->address, REG_GO, 0x01, I2C_TIMEOUT);
}
static error_t stop_playback(Device* device) {
const auto* config = GET_CONFIG(device);
auto* i2c = device_get_parent(device);
return i2c_controller_register8_set(i2c, config->address, REG_GO, 0x00, I2C_TIMEOUT);
}
static constexpr HapticApi DRV2605_HAPTIC_API = {
.set_waveform = set_waveform,
.select_library = select_library,
.start_playback = start_playback,
.stop_playback = stop_playback,
};
// endregion
// region Driver lifecycle
static error_t write_waveform_sequence(Device* i2c, uint8_t address, const uint8_t* waveform, uint32_t length) {
for (uint32_t i = 0; i < length && i < 8; i++) {
error_t error = i2c_controller_register8_set(i2c, address, static_cast<uint8_t>(REG_WAVE_SEQUENCE_1 + i), waveform[i], I2C_TIMEOUT);
if (error != ERROR_NONE) {
return error;
}
}
return ERROR_NONE;
}
static error_t start(Device* device) {
auto* i2c = device_get_parent(device);
check(device_get_type(i2c) == &I2C_CONTROLLER_TYPE);
const auto* config = GET_CONFIG(device);
uint8_t status = 0;
if (i2c_controller_register8_get(i2c, config->address, REG_STATUS, &status, I2C_TIMEOUT) != ERROR_NONE) {
LOG_E(TAG, "Failed to read status");
return ERROR_RESOURCE;
}
// Bits[7:5]: 3=DRV2605, 4=DRV2604, 6=DRV2604L, 7=DRV2605L.
uint8_t chip_id = status >> 5;
if (chip_id != 3 && chip_id != 4 && chip_id != 6 && chip_id != 7) {
LOG_E(TAG, "Unknown chip id %02X", chip_id);
return ERROR_RESOURCE;
}
i2c_controller_register8_set(i2c, config->address, REG_MODE, 0x00, I2C_TIMEOUT); // Exit standby
i2c_controller_register8_set(i2c, config->address, REG_REALTIME_PLAYBACK_INPUT, 0x00, I2C_TIMEOUT); // Disable
// Baseline single strong-click sequence (matches Drv2605::setWaveFormForClick()).
write_waveform_sequence(i2c, config->address, (const uint8_t[]) { 1, 0 }, 2);
i2c_controller_register8_set(i2c, config->address, REG_OVERDRIVE_TIME_OFFSET, 0x00, I2C_TIMEOUT);
i2c_controller_register8_set(i2c, config->address, REG_SUSTAIN_TIME_OFFSET_POSITIVE, 0x00, I2C_TIMEOUT);
i2c_controller_register8_set(i2c, config->address, REG_SUSTAIN_TIME_OFFSET_NEGATIVE, 0x00, I2C_TIMEOUT);
i2c_controller_register8_set(i2c, config->address, REG_BRAKE_TIME_OFFSET, 0x00, I2C_TIMEOUT);
i2c_controller_register8_set(i2c, config->address, REG_AUDIO_INPUT_LEVEL_MAX, 0x64, I2C_TIMEOUT);
// ERM open loop.
i2c_controller_register8_reset_bits(i2c, config->address, REG_FEEDBACK, FEEDBACK_N_ERM_LRA, I2C_TIMEOUT);
i2c_controller_register8_set_bits(i2c, config->address, REG_CONTROL3, CONTROL3_ERM_OPEN_LOOP, I2C_TIMEOUT);
if (config->autoplay) {
const uint8_t* waveform = config->startup_waveform;
uint32_t length = config->startup_waveform_length;
if (waveform == nullptr || length == 0) {
waveform = DEFAULT_STARTUP_WAVEFORM;
length = sizeof(DEFAULT_STARTUP_WAVEFORM);
}
write_waveform_sequence(i2c, config->address, waveform, length);
i2c_controller_register8_set(i2c, config->address, REG_GO, 0x01, I2C_TIMEOUT);
}
return ERROR_NONE;
}
static error_t stop(Device*) {
return ERROR_NONE;
}
// endregion
Driver drv2605_driver = {
.name = "drv2605",
.compatible = (const char*[]) { "ti,drv2605", nullptr },
.start_device = start,
.stop_device = stop,
.api = &DRV2605_HAPTIC_API,
.device_type = &HAPTIC_TYPE,
.owner = &drv2605_module,
.internal = nullptr
};
}