blob: a1de61512f2b376435f1600d58bfeac15de6ede6 [file] [log] [blame]
/*
* 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)