/* * ZephCore - Haptic feedback (DRV2605) * Copyright (c) 2026 ZephCore * SPDX-License-Identifier: MIT */ #include "haptic.h" #include #include #include LOG_MODULE_REGISTER(zephcore_haptic, CONFIG_ZEPHCORE_SENSORS_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(ti_drv2605) #include #include #include #define HAPTIC_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(ti_drv2605) #if DT_PROP(HAPTIC_NODE, zephyr_deferred_init) /* Wake a deferred-init DRV2605 and run its init here, where the part's supply * rail has been up since boot instead of microseconds. * * The enable pin is asserted *before* device_init() on purpose. Upstream's * drv2605_init() spends its DRV2605_POWER_UP_DELAY_US before it configures the * GPIOs, so EN rises and the very next thing it does is read the status * register over I2C — no settling time at all between the two. Raising EN * ourselves and waiting turns that race into a plain sequence; the driver's own * gpio_pin_configure_dt() to OUTPUT_ACTIVE afterwards is then a no-op. */ static void haptic_deferred_init(const struct device *dev) { static const struct gpio_dt_spec en = GPIO_DT_SPEC_GET_OR(HAPTIC_NODE, en_gpios, {0}); int rc; if (en.port != NULL && gpio_is_ready_dt(&en)) { if (gpio_pin_configure_dt(&en, GPIO_OUTPUT_ACTIVE) == 0) { /* 250 us is the datasheet figure; 1 ms costs nothing on a * one-shot boot path and covers the rail as well. */ k_msleep(1); } } rc = device_init(dev); LOG_DBG("haptic deferred init: rc=%d ready=%d", rc, (int)device_is_ready(dev)); } #else static inline void haptic_deferred_init(const struct device *dev) { ARG_UNUSED(dev); } #endif static const struct device *haptic_dev; static bool haptic_enabled = true; /* Library effect 1 = "Strong Click - 100%", short enough to sit under a * notification melody without outlasting it. */ static struct drv2605_rom_data rom_data = { .trigger = DRV2605_MODE_INTERNAL_TRIGGER, .library = DRV2605_LIBRARY_LRA, .seq_regs = { 1 }, }; int haptic_init(void) { const union drv2605_config_data cfg = { .rom_data = &rom_data }; int rc; haptic_dev = DEVICE_DT_GET(HAPTIC_NODE); if (!device_is_ready(haptic_dev)) { haptic_deferred_init(haptic_dev); } if (!device_is_ready(haptic_dev)) { /* The DRV2605 driver logs its own reason at debug level, so say * enough here to tell "chip absent" from "bus down". */ const struct device *bus = DEVICE_DT_GET(DT_BUS(HAPTIC_NODE)); LOG_WRN("haptic %s not ready (addr 0x%02x, bus %s %s) — " "build with CONFIG_HAPTICS_LOG_LEVEL_DBG for the cause", haptic_dev->name, (unsigned int)DT_REG_ADDR(HAPTIC_NODE), bus->name, device_is_ready(bus) ? "up" : "DOWN"); haptic_dev = NULL; return -ENODEV; } rc = drv2605_haptic_config(haptic_dev, DRV2605_HAPTICS_SOURCE_ROM, &cfg); if (rc < 0) { LOG_WRN("haptic config failed: %d", rc); haptic_dev = NULL; return rc; } LOG_INF("haptic driver ready"); return 0; } void haptic_pulse(void) { if (!haptic_dev || !haptic_enabled) { return; } (void)haptics_start_output(haptic_dev); } void haptic_set_enabled(bool enabled) { haptic_enabled = enabled; if (!enabled && haptic_dev) { (void)haptics_stop_output(haptic_dev); } } bool haptic_available(void) { return haptic_dev != NULL; } #else /* no ti,drv2605 node — link-compatible stubs */ int haptic_init(void) { return -ENODEV; } void haptic_pulse(void) { } void haptic_set_enabled(bool enabled) { ARG_UNUSED(enabled); } bool haptic_available(void) { return false; } #endif