zephyr/drivers/haptics/drv2605.c
Ricardo Rivera-Matos 04dae3acbf drivers: haptics: drv2605: Hard codes open loop mode for LRA mode
Enables open loop operation when initializing into LRA mode. Open loop
operation is enabled by default for ERM mode. This is hard coded for LRA
mode because calibration is currently unsupported.

Signed-off-by: Ricardo Rivera-Matos <rriveram@opensource.cirrus.com>
2024-08-20 14:52:32 -04:00

645 lines
17 KiB
C

/*
* Copyright 2024 Cirrus Logic, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*
* DRV2605 Datasheet: https://www.ti.com/lit/gpn/drv2605
*/
#define DT_DRV_COMPAT ti_drv2605
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/haptics.h>
#include <zephyr/drivers/haptics/drv2605.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/haptics.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(DRV2605, CONFIG_HAPTICS_LOG_LEVEL);
#define DRV2605_REG_STATUS 0x0
#define DRV2605_DEVICE_ID GENMASK(7, 5)
#define DRV2605_DEVICE_ID_DRV2605 0x3
#define DRV2605_DEVICE_ID_DRV2605L 0x7
#define DRV2605_DIAG_RESULT BIT(3)
#define DRV2605_FB_STS BIT(2)
#define DRV2605_OVER_TEMP BIT(1)
#define DRV2605_OC_DETECT BIT(0)
#define DRV2605_REG_MODE 0x1
#define DRV2605_DEV_RESET BIT(7)
#define DRV2605_STANDBY BIT(6)
#define DRV2605_MODE GENMASK(2, 0)
#define DRV2605_REG_RT_PLAYBACK_INPUT 0x2
#define DRV2605_REG_UNNAMED 0x3
#define DRV2605_HI_Z_OUTPUT BIT(4)
#define DRV2605_LIBRARY_SEL GENMASK(2, 0)
#define DRV2605_REG_WAVEFORM_SEQUENCER 0x4
#define DRV2605_WAIT BIT(7)
#define DRV2605_WAV_FRM_SEQ GENMASK(6, 0)
#define DRV2605_REG_GO 0xc
#define DRV2605_GO BIT(0)
#define DRV2605_REG_OVERDRIVE_TIME_OFFSET 0xd
#define DRV2605_REG_SUSTAIN_TIME_OFFSET_POS 0xe
#define DRV2605_REG_SUSTAIN_TIME_OFFSET_NEG 0xf
#define DRV2605_REG_BRAKE_TIME_OFFSET 0x10
#define DRV2605_TIME_STEP_MS 5
#define DRV2605_REG_AUDIO_TO_VIBE_CONTROL 0x11
#define DRV2605_ATH_PEAK_TIME GENMASK(3, 2)
#define DRV2605_ATH_FILTER GENMASK(1, 0)
#define DRV2605_REG_AUDIO_TO_VIBE_MIN_INPUT_LEVEL 0x12
#define DRV2605_REG_AUDIO_TO_VIBE_MAX_INPUT_LEVEL 0x13
#define DRV2605_ATH_INPUT_STEP_UV (1800000 / 255)
#define DRV2605_REG_AUDIO_TO_VIBE_MIN_OUTPUT_DRIVE 0x14
#define DRV2605_REG_AUDIO_TO_VIBE_MAX_OUTPUT_DRIVE 0x15
#define DRV2605_ATH_OUTPUT_DRIVE_PCT (100 * 255)
#define DRV2605_REG_RATED_VOLTAGE 0x16
#define DRV2605_REG_OVERDRIVE_CLAMP_VOLTAGE 0x17
#define DRV2605_REG_AUTO_CAL_COMP_RESULT 0x18
#define DRV2605_REG_AUTO_CAL_BACK_EMF_RESULT 0x19
#define DRV2605_REG_FEEDBACK_CONTROL 0x1a
#define DRV2605_N_ERM_LRA BIT(7)
#define DRV2605_FB_BRAKE_FACTOR GENMASK(6, 4)
#define DRV2605_LOOP_GAIN GENMASK(3, 2)
#define DRV2605_BEMF_GAIN GENMASK(1, 0)
#define DRV2605_ACTUATOR_MODE_ERM 0
#define DRV2605_ACTUATOR_MODE_LRA 1
#define DRV2605_REG_CONTROL1 0x1b
#define DRV2605_STARTUP_BOOST BIT(7)
#define DRV2605_AC_COUPLE BIT(5)
#define DRV2605_DRIVE_TIME GENMASK(4, 0)
#define DRV2605_REG_CONTROL2 0x1c
#define DRV2605_BIDIR_INPUT BIT(7)
#define DRV2605_BRAKE_STABILIZER BIT(6)
#define DRV2605_SAMPLE_TIME GENMASK(5, 4)
#define DRV2605_BLANKING_TIME GENMASK(3, 2)
#define DRV2605_IDISS_TIME GENMASK(1, 0)
#define DRV2605_REG_CONTROL3 0x1d
#define DRV2605_NG_THRESH GENMASK(7, 6)
#define DRV2605_ERM_OPEN_LOOP BIT(5)
#define DRV2605_SUPPLY_COMP_DIS BIT(4)
#define DRV2605_DATA_FORMAT_RTP BIT(3)
#define DRV2605_LRA_DRIVE_MODE BIT(2)
#define DRV2605_N_PWM_ANALOG BIT(1)
#define DRV2605_LRA_OPEN_LOOP BIT(0)
#define DRV2605_REG_CONTROL4 0x1e
#define DRV2605_ZERO_CROSSING_TIME GENMASK(7, 6)
#define DRV2605_AUTO_CAL_TIME GENMASK(5, 4)
#define DRV2605_OTP_STATUS BIT(2)
#define DRV2605_OTP_PROGRAM BIT(0)
#define DRV2605_REG_BATT_VOLTAGE_MONITOR 0x21
#define DRV2605_VBAT_STEP_UV (5600000 / 255)
#define DRV2605_REG_LRA_RESONANCE_PERIOD 0x22
#define DRV2605_POWER_UP_DELAY_US 250
#define DRV2605_VOLTAGE_SCALE_FACTOR_MV 5600
#define DRV2605_CALCULATE_VOLTAGE(_volt) ((_volt * 255) / DRV2605_VOLTAGE_SCALE_FACTOR_MV)
struct drv2605_config {
struct i2c_dt_spec i2c;
struct gpio_dt_spec en_gpio;
struct gpio_dt_spec in_trig_gpio;
uint8_t feedback_brake_factor;
uint8_t loop_gain;
uint8_t rated_voltage;
uint8_t overdrive_clamp_voltage;
uint8_t auto_cal_time;
uint8_t drive_time;
bool actuator_mode;
};
struct drv2605_data {
const struct device *dev;
struct k_work rtp_work;
const struct drv2605_rtp_data *rtp_data;
enum drv2605_mode mode;
};
static inline int drv2605_haptic_config_audio(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
int ret;
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_CONTROL3, DRV2605_N_PWM_ANALOG,
DRV2605_N_PWM_ANALOG);
if (ret < 0) {
return ret;
}
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_CONTROL1, DRV2605_AC_COUPLE,
DRV2605_AC_COUPLE);
if (ret < 0) {
return ret;
}
data->mode = DRV2605_MODE_AUDIO_TO_VIBE;
return 0;
}
static inline int drv2605_haptic_config_pwm_analog(const struct device *dev, const bool analog)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
uint8_t value = 0;
int ret;
if (analog) {
value = DRV2605_N_PWM_ANALOG;
}
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_CONTROL3, DRV2605_N_PWM_ANALOG,
value);
if (ret < 0) {
return ret;
}
data->mode = DRV2605_MODE_PWM_ANALOG_INPUT;
return 0;
}
static void drv2605_rtp_work_handler(struct k_work *work)
{
struct drv2605_data *data = CONTAINER_OF(work, struct drv2605_data, rtp_work);
const struct drv2605_rtp_data *rtp_data = data->rtp_data;
const struct drv2605_config *config = data->dev->config;
int i;
for (i = 0; i < rtp_data->size; i++) {
i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_RT_PLAYBACK_INPUT,
rtp_data->rtp_input[i]);
k_usleep(rtp_data->rtp_hold_us[i]);
}
}
static inline int drv2605_haptic_config_rtp(const struct device *dev,
const struct drv2605_rtp_data *rtp_data)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
int ret;
data->rtp_data = rtp_data;
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_RT_PLAYBACK_INPUT, 0);
if (ret < 0) {
return ret;
}
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_MODE,
(uint8_t)DRV2605_MODE_RTP);
if (ret < 0) {
return ret;
}
data->mode = DRV2605_MODE_RTP;
return 0;
}
static inline int drv2605_haptic_config_rom(const struct device *dev,
const struct drv2605_rom_data *rom_data)
{
const struct drv2605_config *config = dev->config;
uint8_t reg_addr = DRV2605_REG_WAVEFORM_SEQUENCER;
struct drv2605_data *data = dev->data;
int i, ret;
switch (rom_data->trigger) {
case DRV2605_MODE_INTERNAL_TRIGGER:
case DRV2605_MODE_EXTERNAL_EDGE_TRIGGER:
case DRV2605_MODE_EXTERNAL_LEVEL_TRIGGER:
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_MODE,
(uint8_t)rom_data->trigger);
if (ret < 0) {
return ret;
}
data->mode = rom_data->trigger;
break;
default:
return -EINVAL;
}
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_UNNAMED, DRV2605_LIBRARY_SEL,
(uint8_t)rom_data->library);
if (ret < 0) {
return ret;
}
for (i = 0; i < DRV2605_WAVEFORM_SEQUENCER_MAX; i++) {
ret = i2c_reg_write_byte_dt(&config->i2c, reg_addr, rom_data->seq_regs[i]);
if (ret < 0) {
return ret;
}
reg_addr++;
if (rom_data->seq_regs[i] == 0U) {
break;
}
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_OVERDRIVE_TIME_OFFSET,
rom_data->overdrive_time);
if (ret < 0) {
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_SUSTAIN_TIME_OFFSET_POS,
rom_data->sustain_pos_time);
if (ret < 0) {
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_SUSTAIN_TIME_OFFSET_NEG,
rom_data->sustain_neg_time);
if (ret < 0) {
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_BRAKE_TIME_OFFSET,
rom_data->brake_time);
if (ret < 0) {
return ret;
}
return 0;
}
int drv2605_haptic_config(const struct device *dev, enum drv2605_haptics_source source,
const union drv2605_config_data *config_data)
{
switch (source) {
case DRV2605_HAPTICS_SOURCE_ROM:
return drv2605_haptic_config_rom(dev, config_data->rom_data);
case DRV2605_HAPTICS_SOURCE_RTP:
return drv2605_haptic_config_rtp(dev, config_data->rtp_data);
case DRV2605_HAPTICS_SOURCE_AUDIO:
return drv2605_haptic_config_audio(dev);
case DRV2605_HAPTICS_SOURCE_PWM:
return drv2605_haptic_config_pwm_analog(dev, false);
case DRV2605_HAPTICS_SOURCE_ANALOG:
return drv2605_haptic_config_pwm_analog(dev, true);
default:
return -ENOTSUP;
}
return 0;
}
static inline int drv2605_edge_mode_event(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
int ret;
ret = gpio_pin_set_dt(&config->in_trig_gpio, 1);
if (ret < 0) {
return ret;
}
return gpio_pin_set_dt(&config->in_trig_gpio, 0);
}
static int drv2605_stop_output(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
uint8_t value;
int ret;
switch (data->mode) {
case DRV2605_MODE_DIAGNOSTICS:
case DRV2605_MODE_AUTO_CAL:
ret = i2c_reg_read_byte_dt(&config->i2c, DRV2605_REG_GO, &value);
if (ret < 0) {
return ret;
}
if (FIELD_GET(DRV2605_GO, value)) {
LOG_DBG("Playback mode: %d is uninterruptible", data->mode);
return -EBUSY;
}
break;
case DRV2605_MODE_INTERNAL_TRIGGER:
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_GO, DRV2605_GO, 0);
case DRV2605_MODE_EXTERNAL_EDGE_TRIGGER:
return drv2605_edge_mode_event(dev);
case DRV2605_MODE_EXTERNAL_LEVEL_TRIGGER:
return gpio_pin_set_dt(&config->in_trig_gpio, 0);
case DRV2605_MODE_PWM_ANALOG_INPUT:
case DRV2605_MODE_AUDIO_TO_VIBE:
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_MODE,
(uint8_t)DRV2605_MODE_INTERNAL_TRIGGER);
if (ret < 0) {
return ret;
}
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_GO, DRV2605_GO, 0);
case DRV2605_MODE_RTP:
return k_work_cancel(&data->rtp_work);
default:
return -ENOTSUP;
}
return 0;
}
static int drv2605_start_output(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
switch (data->mode) {
case DRV2605_MODE_DIAGNOSTICS:
case DRV2605_MODE_AUTO_CAL:
case DRV2605_MODE_INTERNAL_TRIGGER:
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_GO, DRV2605_GO, DRV2605_GO);
case DRV2605_MODE_EXTERNAL_EDGE_TRIGGER:
return drv2605_edge_mode_event(dev);
case DRV2605_MODE_EXTERNAL_LEVEL_TRIGGER:
return gpio_pin_set_dt(&config->in_trig_gpio, 1);
case DRV2605_MODE_AUDIO_TO_VIBE:
case DRV2605_MODE_PWM_ANALOG_INPUT:
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_MODE,
(uint8_t)data->mode);
case DRV2605_MODE_RTP:
return k_work_submit(&data->rtp_work);
default:
return -ENOTSUP;
}
return 0;
}
#ifdef CONFIG_PM_DEVICE
static int drv2605_pm_action(const struct device *dev, enum pm_device_action action)
{
const struct drv2605_config *config = dev->config;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_STANDBY, 0);
case PM_DEVICE_ACTION_SUSPEND:
return i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_STANDBY,
DRV2605_STANDBY);
case PM_DEVICE_ACTION_TURN_OFF:
if (config->en_gpio.port != NULL) {
gpio_pin_set_dt(&config->en_gpio, 0);
}
break;
case PM_DEVICE_ACTION_TURN_ON:
if (config->en_gpio.port != NULL) {
gpio_pin_set_dt(&config->en_gpio, 1);
}
break;
default:
return -ENOTSUP;
}
return 0;
}
#endif
static int drv2605_hw_config(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
uint8_t mask, value;
int ret;
value = FIELD_PREP(DRV2605_N_ERM_LRA, config->actuator_mode) |
FIELD_PREP(DRV2605_FB_BRAKE_FACTOR, config->feedback_brake_factor) |
FIELD_PREP(DRV2605_LOOP_GAIN, config->loop_gain);
mask = DRV2605_N_ERM_LRA | DRV2605_FB_BRAKE_FACTOR | DRV2605_LOOP_GAIN;
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_FEEDBACK_CONTROL, mask, value);
if (ret < 0) {
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_RATED_VOLTAGE, config->rated_voltage);
if (ret < 0) {
return ret;
}
ret = i2c_reg_write_byte_dt(&config->i2c, DRV2605_REG_OVERDRIVE_CLAMP_VOLTAGE,
config->overdrive_clamp_voltage);
if (ret < 0) {
return ret;
}
if (config->actuator_mode == DRV2605_ACTUATOR_MODE_LRA) {
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_CONTROL3,
DRV2605_LRA_OPEN_LOOP, DRV2605_LRA_OPEN_LOOP);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int drv2605_reset(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
int retries = 5, ret;
uint8_t value;
i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_STANDBY, 0);
ret = i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_DEV_RESET,
DRV2605_DEV_RESET);
if (ret < 0) {
return ret;
}
k_msleep(100);
while (retries > 0) {
i2c_reg_read_byte_dt(&config->i2c, DRV2605_REG_MODE, &value);
if ((value & DRV2605_DEV_RESET) == 0U) {
i2c_reg_update_byte_dt(&config->i2c, DRV2605_REG_MODE, DRV2605_STANDBY, 0);
return 0;
}
retries--;
k_usleep(10000);
}
return -ETIMEDOUT;
}
static int drv2605_check_devid(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
uint8_t value;
int ret;
ret = i2c_reg_read_byte_dt(&config->i2c, DRV2605_REG_STATUS, &value);
if (ret < 0) {
return ret;
}
value = FIELD_GET(DRV2605_DEVICE_ID, value);
switch (value) {
case DRV2605_DEVICE_ID_DRV2605:
case DRV2605_DEVICE_ID_DRV2605L:
break;
default:
LOG_ERR("Invalid device ID found");
return -ENOTSUP;
}
LOG_DBG("Found DRV2605, DEVID: 0x%x", value);
return 0;
}
static int drv2605_gpio_config(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
int ret;
if (config->en_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->en_gpio)) {
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->en_gpio, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return ret;
}
}
if (config->in_trig_gpio.port != NULL) {
if (!gpio_is_ready_dt(&config->in_trig_gpio)) {
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->in_trig_gpio, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
return ret;
}
}
return 0;
}
static int drv2605_init(const struct device *dev)
{
const struct drv2605_config *config = dev->config;
struct drv2605_data *data = dev->data;
int ret;
data->dev = dev;
k_usleep(DRV2605_POWER_UP_DELAY_US);
if (!i2c_is_ready_dt(&config->i2c)) {
return -ENODEV;
}
k_work_init(&data->rtp_work, drv2605_rtp_work_handler);
ret = drv2605_gpio_config(dev);
if (ret < 0) {
LOG_DBG("Failed to allocate GPIOs: %d", ret);
return ret;
}
ret = drv2605_check_devid(dev);
if (ret < 0) {
return ret;
}
ret = drv2605_reset(dev);
if (ret < 0) {
LOG_DBG("Failed to reset device: %d", ret);
return ret;
}
ret = drv2605_hw_config(dev);
if (ret < 0) {
LOG_DBG("Failed to configure device: %d", ret);
return ret;
}
return 0;
}
static const struct haptics_driver_api drv2605_driver_api = {
.start_output = &drv2605_start_output,
.stop_output = &drv2605_stop_output,
};
#define HAPTICS_DRV2605_DEFINE(inst) \
\
static const struct drv2605_config drv2605_config_##inst = { \
.i2c = I2C_DT_SPEC_INST_GET(inst), \
.en_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, en_gpios, {}), \
.in_trig_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, in_trig_gpios, {}), \
.feedback_brake_factor = DT_INST_ENUM_IDX(inst, feedback_brake_factor), \
.loop_gain = DT_INST_ENUM_IDX(inst, loop_gain), \
.actuator_mode = DT_INST_ENUM_IDX(inst, actuator_mode), \
.rated_voltage = DRV2605_CALCULATE_VOLTAGE(DT_INST_PROP(inst, vib_rated_mv)), \
.overdrive_clamp_voltage = \
DRV2605_CALCULATE_VOLTAGE(DT_INST_PROP(inst, vib_overdrive_mv)), \
}; \
\
static struct drv2605_data drv2605_data_##inst = { \
.mode = DRV2605_MODE_INTERNAL_TRIGGER, \
}; \
\
PM_DEVICE_DT_INST_DEFINE(inst, drv2605_pm_action); \
\
DEVICE_DT_INST_DEFINE(inst, drv2605_init, PM_DEVICE_DT_INST_GET(inst), \
&drv2605_data_##inst, &drv2605_config_##inst, POST_KERNEL, \
CONFIG_HAPTICS_INIT_PRIORITY, &drv2605_driver_api);
DT_INST_FOREACH_STATUS_OKAY(HAPTICS_DRV2605_DEFINE)