diff --git a/drivers/stepper/adi_tmc/CMakeLists.txt b/drivers/stepper/adi_tmc/CMakeLists.txt index 51281754394..75ad56aa4cf 100644 --- a/drivers/stepper/adi_tmc/CMakeLists.txt +++ b/drivers/stepper/adi_tmc/CMakeLists.txt @@ -7,3 +7,4 @@ zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC_SPI adi_tmc_spi.c) zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC2209 adi_tmc22xx_stepper_controller.c) zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC50XX adi_tmc50xx_stepper_controller.c) +zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC51XX adi_tmc51xx_stepper_controller.c) diff --git a/drivers/stepper/adi_tmc/adi_tmc51xx_stepper_controller.c b/drivers/stepper/adi_tmc/adi_tmc51xx_stepper_controller.c new file mode 100644 index 00000000000..3953d3ac67b --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc51xx_stepper_controller.c @@ -0,0 +1,690 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_tmc51xx + +#include + +#include +#include + +#include "adi_tmc_spi.h" +#include "adi_tmc5xxx_common.h" + +#include +LOG_MODULE_REGISTER(tmc51xx, CONFIG_STEPPER_LOG_LEVEL); + +struct tmc51xx_data { + struct k_sem sem; + struct k_work_delayable stallguard_dwork; + /* Work item to run the callback in a thread context. */ +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + struct k_work_delayable rampstat_callback_dwork; +#endif + /* device pointer required to access config in k_work */ + const struct device *stepper; + stepper_event_callback_t callback; + void *event_cb_user_data; +}; + +struct tmc51xx_config { + const uint32_t gconf; + struct spi_dt_spec spi; + const uint32_t clock_frequency; + const uint16_t default_micro_step_res; + const int8_t sg_threshold; + const bool is_sg_enabled; + const uint32_t sg_velocity_check_interval_ms; + const uint32_t sg_threshold_velocity; +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN + const struct tmc_ramp_generator_data default_ramp_config; +#endif +}; + +static int tmc51xx_write(const struct device *dev, const uint8_t reg_addr, const uint32_t reg_val) +{ + const struct tmc51xx_config *config = dev->config; + struct tmc51xx_data *data = dev->data; + const struct spi_dt_spec bus = config->spi; + int err; + + k_sem_take(&data->sem, K_FOREVER); + + err = tmc_spi_write_register(&bus, TMC5XXX_WRITE_BIT, reg_addr, reg_val); + + k_sem_give(&data->sem); + + if (err) { + LOG_ERR("Failed to write register 0x%x with value 0x%x", reg_addr, reg_val); + return err; + } + return 0; +} + +static int tmc51xx_read(const struct device *dev, const uint8_t reg_addr, uint32_t *reg_val) +{ + const struct tmc51xx_config *config = dev->config; + struct tmc51xx_data *data = dev->data; + const struct spi_dt_spec bus = config->spi; + int err; + + k_sem_take(&data->sem, K_FOREVER); + + err = tmc_spi_read_register(&bus, TMC5XXX_ADDRESS_MASK, reg_addr, reg_val); + + k_sem_give(&data->sem); + + if (err) { + LOG_ERR("Failed to read register 0x%x", reg_addr); + return err; + } + return 0; +} + +static int tmc51xx_stepper_set_event_callback(const struct device *dev, + stepper_event_callback_t callback, void *user_data) +{ + struct tmc51xx_data *data = dev->data; + + data->callback = callback; + data->event_cb_user_data = user_data; + return 0; +} + +static int stallguard_enable(const struct device *dev, const bool enable) +{ + const struct tmc51xx_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_SWMODE, ®_value); + if (err) { + LOG_ERR("Failed to read SWMODE register"); + return -EIO; + } + + if (enable) { + reg_value |= TMC5XXX_SW_MODE_SG_STOP_ENABLE; + + int32_t actual_velocity; + + err = tmc51xx_read(dev, TMC51XX_VACTUAL, + &actual_velocity); + if (err) { + LOG_ERR("Failed to read VACTUAL register"); + return -EIO; + } + + actual_velocity = (actual_velocity << (31 - TMC_RAMP_VACTUAL_SHIFT)) >> + (31 - TMC_RAMP_VACTUAL_SHIFT); + LOG_DBG("actual velocity: %d", actual_velocity); + + if (abs(actual_velocity) < config->sg_threshold_velocity) { + return -EAGAIN; + } + } else { + reg_value &= ~TMC5XXX_SW_MODE_SG_STOP_ENABLE; + } + err = tmc51xx_write(dev, TMC51XX_SWMODE, reg_value); + if (err) { + LOG_ERR("Failed to write SWMODE register"); + return -EIO; + } + return 0; +} + +static void stallguard_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct tmc51xx_data *stepper_data = + CONTAINER_OF(dwork, struct tmc51xx_data, stallguard_dwork); + const struct device *dev = stepper_data->stepper; + const struct tmc51xx_config *config = dev->config; + int err; + + err = stallguard_enable(dev, true); + if (err == -EAGAIN) { + LOG_ERR("retrying stallguard activation"); + k_work_reschedule(dwork, K_MSEC(config->sg_velocity_check_interval_ms)); + } + if (err == -EIO) { + LOG_ERR("Failed to enable stallguard because of I/O error"); + return; + } +} + +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + +static void execute_callback(const struct device *dev, const enum stepper_event event) +{ + struct tmc51xx_data *data = dev->data; + + if (!data->callback) { + LOG_WRN_ONCE("No callback registered"); + return; + } + data->callback(dev, event, data->event_cb_user_data); +} + +static void rampstat_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + + struct tmc51xx_data *stepper_data = + CONTAINER_OF(dwork, struct tmc51xx_data, rampstat_callback_dwork); + const struct device *dev = stepper_data->stepper; + + __ASSERT_NO_MSG(dev != NULL); + + uint32_t drv_status; + int err; + + err = tmc51xx_read(dev, TMC51XX_DRVSTATUS, + &drv_status); + if (err != 0) { + LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name); + return; + } + + if (FIELD_GET(TMC5XXX_DRV_STATUS_SG_STATUS_MASK, drv_status) == 1U) { + LOG_INF("%s: Stall detected", dev->name); + err = tmc51xx_write(dev, + TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_HOLD_MODE); + if (err != 0) { + LOG_ERR("%s: Failed to stop motor", dev->name); + return; + } + } + + uint32_t rampstat_value; + + err = tmc51xx_read(dev, TMC51XX_RAMPSTAT, + &rampstat_value); + if (err != 0) { + LOG_ERR("%s: Failed to read RAMPSTAT register", dev->name); + return; + } + + const uint8_t ramp_stat_values = FIELD_GET(TMC5XXX_RAMPSTAT_INT_MASK, rampstat_value); + + if (ramp_stat_values > 0) { + switch (ramp_stat_values) { + + case TMC5XXX_STOP_LEFT_EVENT: + LOG_DBG("RAMPSTAT %s:Left end-stop detected", dev->name); + execute_callback(dev, + STEPPER_EVENT_LEFT_END_STOP_DETECTED); + break; + + case TMC5XXX_STOP_RIGHT_EVENT: + LOG_DBG("RAMPSTAT %s:Right end-stop detected", dev->name); + execute_callback(dev, + STEPPER_EVENT_RIGHT_END_STOP_DETECTED); + break; + + case TMC5XXX_POS_REACHED_EVENT: + LOG_DBG("RAMPSTAT %s:Position reached", dev->name); + execute_callback(dev, STEPPER_EVENT_STEPS_COMPLETED); + break; + + case TMC5XXX_STOP_SG_EVENT: + LOG_DBG("RAMPSTAT %s:Stall detected", dev->name); + stallguard_enable(dev, false); + execute_callback(dev, STEPPER_EVENT_STALL_DETECTED); + break; + default: + LOG_ERR("Illegal ramp stat bit field"); + break; + } + } else { + k_work_reschedule( + &stepper_data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); + } +} + +#endif + +static int tmc51xx_stepper_enable(const struct device *dev) +{ + LOG_DBG("Enabling Stepper motor controller %s", dev->name); + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + + reg_value |= TMC5XXX_CHOPCONF_DRV_ENABLE_MASK; + + return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); +} + +static int tmc51xx_stepper_disable(const struct device *dev) +{ + LOG_DBG("Disabling Stepper motor controller %s", dev->name); + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + + reg_value &= ~TMC5XXX_CHOPCONF_DRV_ENABLE_MASK; + + return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); +} + +static int tmc51xx_stepper_is_moving(const struct device *dev, bool *is_moving) +{ + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_DRVSTATUS, ®_value); + + if (err != 0) { + LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name); + return -EIO; + } + + *is_moving = (FIELD_GET(TMC5XXX_DRV_STATUS_STST_BIT, reg_value) == 1U); + LOG_DBG("Stepper motor controller %s is moving: %d", dev->name, *is_moving); + return 0; +} + +int tmc51xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity) +{ + const struct tmc51xx_config *config = dev->config; + const uint32_t clock_frequency = config->clock_frequency; + uint32_t velocity_fclk; + int err; + + velocity_fclk = tmc5xxx_calculate_velocity_from_hz_to_fclk(velocity, clock_frequency); + + err = tmc51xx_write(dev, TMC51XX_VMAX, velocity_fclk); + if (err != 0) { + LOG_ERR("%s: Failed to set max velocity", dev->name); + return -EIO; + } + return 0; +} + +static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution res) +{ + if (!VALID_MICRO_STEP_RES(res)) { + LOG_ERR("Invalid micro step resolution %d", res); + return -ENOTSUP; + } + + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + + reg_value &= ~TMC5XXX_CHOPCONF_MRES_MASK; + reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) + << TMC5XXX_CHOPCONF_MRES_SHIFT); + + err = tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); + if (err != 0) { + return -EIO; + } + + LOG_DBG("Stepper motor controller %s set micro step resolution to 0x%x", dev->name, + reg_value); + return 0; +} + +static int tmc51xx_stepper_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *res) +{ + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + reg_value &= TMC5XXX_CHOPCONF_MRES_MASK; + reg_value >>= TMC5XXX_CHOPCONF_MRES_SHIFT; + *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - reg_value)); + LOG_DBG("Stepper motor controller %s get micro step resolution: %d", dev->name, *res); + return 0; +} + +static int tmc51xx_stepper_set_reference_position(const struct device *dev, const int32_t position) +{ + int err; + + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_HOLD_MODE); + if (err != 0) { + return -EIO; + } + + err = tmc51xx_write(dev, TMC51XX_XACTUAL, position); + if (err != 0) { + return -EIO; + } + LOG_DBG("Stepper motor controller %s set actual position to %d", dev->name, position); + return 0; +} + +static int tmc51xx_stepper_get_actual_position(const struct device *dev, int32_t *position) +{ + int err; + + err = tmc51xx_read(dev, TMC51XX_XACTUAL, position); + if (err != 0) { + return -EIO; + } + LOG_DBG("%s actual position: %d", dev->name, *position); + return 0; +} + +static int tmc51xx_stepper_move_to(const struct device *dev, const int32_t micro_steps) +{ + LOG_DBG("%s set target position to %d", dev->name, micro_steps); + const struct tmc51xx_config *config = dev->config; + struct tmc51xx_data *data = dev->data; + int err; + + if (config->is_sg_enabled) { + stallguard_enable(dev, false); + } + + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_POSITIONING_MODE); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_XTARGET, micro_steps); + if (err != 0) { + return -EIO; + } + + if (config->is_sg_enabled) { + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + if (data->callback) { + k_work_reschedule( + &data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); + } +#endif + return 0; +} + +static int tmc51xx_stepper_move_by(const struct device *dev, const int32_t micro_steps) +{ + int err; + int32_t position; + + err = stepper_get_actual_position(dev, &position); + if (err != 0) { + return -EIO; + } + int32_t target_position = position + micro_steps; + + LOG_DBG("%s moved to %d by steps: %d", dev->name, target_position, micro_steps); + + return tmc51xx_stepper_move_to(dev, target_position); +} + +static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_direction direction) +{ + LOG_DBG("Stepper motor controller %s run", dev->name); + const struct tmc51xx_config *config = dev->config; + struct tmc51xx_data *data = dev->data; + int err; + + if (config->is_sg_enabled) { + err = stallguard_enable(dev, false); + if (err != 0) { + return -EIO; + } + } + + switch (direction) { + case STEPPER_DIRECTION_POSITIVE: + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + break; + + case STEPPER_DIRECTION_NEGATIVE: + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_NEGATIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + break; + } + + if (config->is_sg_enabled) { + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + if (data->callback) { + k_work_reschedule( + &data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); + } +#endif + return 0; +} + +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN + +int tmc51xx_stepper_set_ramp(const struct device *dev, + const struct tmc_ramp_generator_data *ramp_data) +{ + LOG_DBG("Stepper motor controller %s set ramp", dev->name); + int err; + + err = tmc51xx_write(dev, TMC51XX_VSTART, ramp_data->vstart); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_A1, ramp_data->a1); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_AMAX, ramp_data->amax); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_D1, ramp_data->d1); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_DMAX, ramp_data->dmax); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_V1, ramp_data->v1); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_VMAX, ramp_data->vmax); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_VSTOP, ramp_data->vstop); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_TZEROWAIT, + ramp_data->tzerowait); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_THIGH, ramp_data->thigh); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_TCOOLTHRS, ramp_data->tcoolthrs); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_TPWMTHRS, ramp_data->tpwmthrs); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_TPOWER_DOWN, ramp_data->tpowerdown); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_IHOLD_IRUN, ramp_data->iholdrun); + if (err != 0) { + return -EIO; + } + return 0; +} + +#endif + +static int tmc51xx_init(const struct device *dev) +{ + LOG_DBG("TMC51XX stepper motor controller %s initialized", dev->name); + struct tmc51xx_data *data = dev->data; + const struct tmc51xx_config *config = dev->config; + int err; + + k_sem_init(&data->sem, 1, 1); + + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("SPI bus is not ready"); + return -ENODEV; + } + + LOG_DBG("GCONF: %d", config->gconf); + err = tmc51xx_write(dev, TMC5XXX_GCONF, config->gconf); + if (err != 0) { + return -EIO; + } + + /* Read and write GSTAT register to clear any SPI Datagram errors. */ + uint32_t gstat_value; + + err = tmc51xx_read(dev, TMC5XXX_GSTAT, &gstat_value); + if (err != 0) { + return -EIO; + } + + err = tmc51xx_write(dev, TMC5XXX_GSTAT, gstat_value); + if (err != 0) { + return -EIO; + } + + if (config->is_sg_enabled) { + k_work_init_delayable(&data->stallguard_dwork, stallguard_work_handler); + + err = tmc51xx_write(dev, + TMC51XX_SWMODE, BIT(10)); + if (err != 0) { + return -EIO; + } + + LOG_DBG("Setting stall guard to %d with delay %d ms", config->sg_threshold, + config->sg_velocity_check_interval_ms); + if (!IN_RANGE(config->sg_threshold, TMC5XXX_SG_MIN_VALUE, + TMC5XXX_SG_MAX_VALUE)) { + LOG_ERR("Stallguard threshold out of range"); + return -EINVAL; + } + + int32_t stall_guard_threshold = (int32_t)config->sg_threshold; + + err = tmc51xx_write( + dev, TMC51XX_COOLCONF, + stall_guard_threshold << TMC5XXX_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT); + if (err != 0) { + return -EIO; + } + err = stallguard_enable(dev, true); + if (err == -EAGAIN) { + LOG_ERR("retrying stallguard activation"); + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } + } + +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN + err = tmc51xx_stepper_set_ramp(dev, &config->default_ramp_config); + if (err != 0) { + return -EIO; + } +#endif + +#if CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + k_work_init_delayable(&data->rampstat_callback_dwork, rampstat_work_handler); + k_work_reschedule(&data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); +#endif + err = tmc51xx_stepper_set_micro_step_res(dev, config->default_micro_step_res); + if (err != 0) { + return -EIO; + } + return 0; +} + +static DEVICE_API(stepper, tmc51xx_api) = { + .enable = tmc51xx_stepper_enable, + .disable = tmc51xx_stepper_disable, + .is_moving = tmc51xx_stepper_is_moving, + .move_by = tmc51xx_stepper_move_by, + .set_micro_step_res = tmc51xx_stepper_set_micro_step_res, + .get_micro_step_res = tmc51xx_stepper_get_micro_step_res, + .set_reference_position = tmc51xx_stepper_set_reference_position, + .get_actual_position = tmc51xx_stepper_get_actual_position, + .move_to = tmc51xx_stepper_move_to, + .run = tmc51xx_stepper_run, + .set_event_callback = tmc51xx_stepper_set_event_callback, +}; + +#define TMC51XX_DEFINE(inst) \ + BUILD_ASSERT((DT_INST_PROP(inst, clock_frequency) > 0), \ + "clock frequency must be non-zero positive value"); \ + static struct tmc51xx_data tmc51xx_data_##inst = { \ + .stepper = DEVICE_DT_GET(DT_DRV_INST(inst))}; \ + COND_CODE_1(DT_PROP_EXISTS(inst, stallguard_threshold_velocity), \ + BUILD_ASSERT(DT_PROP(inst, stallguard_threshold_velocity), \ + "stallguard threshold velocity must be a positive value"), ()); \ + IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, (CHECK_RAMP_DT_DATA(inst))); \ + static const struct tmc51xx_config tmc51xx_config_##inst = { \ + .gconf = ( \ + (DT_INST_PROP(inst, en_pwm_mode) << TMC51XX_GCONF_EN_PWM_MODE_SHIFT) | \ + (DT_INST_PROP(inst, test_mode) << TMC51XX_GCONF_TEST_MODE_SHIFT) | \ + (DT_INST_PROP(inst, invert_direction) << TMC51XX_GCONF_SHAFT_SHIFT)), \ + .spi = SPI_DT_SPEC_INST_GET(inst, (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ + SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8)), 0), \ + .clock_frequency = DT_INST_PROP(inst, clock_frequency), \ + .default_micro_step_res = DT_INST_PROP(inst, micro_step_res), \ + .sg_threshold = DT_INST_PROP(inst, stallguard2_threshold), \ + .sg_threshold_velocity = DT_INST_PROP(inst, stallguard_threshold_velocity), \ + .sg_velocity_check_interval_ms = DT_INST_PROP(inst, \ + stallguard_velocity_check_interval_ms), \ + .is_sg_enabled = DT_INST_PROP(inst, activate_stallguard2), \ + IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, \ + (.default_ramp_config = TMC_RAMP_DT_SPEC_GET_TMC51XX(inst)))}; \ + DEVICE_DT_INST_DEFINE(inst, tmc51xx_init, NULL, &tmc51xx_data_##inst, \ + &tmc51xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY,\ + &tmc51xx_api); + +DT_INST_FOREACH_STATUS_OKAY(TMC51XX_DEFINE) diff --git a/dts/bindings/stepper/adi/adi,tmc51xx.yaml b/dts/bindings/stepper/adi/adi,tmc51xx.yaml new file mode 100644 index 00000000000..caad1785123 --- /dev/null +++ b/dts/bindings/stepper/adi/adi,tmc51xx.yaml @@ -0,0 +1,108 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S +# SPDX-License-Identifier: Apache-2.0 + +description: | + Analog Devices TMC51XX Stepper Motor Controller + + Example: + + &spi0 { + /* SPI bus options here, not shown */ + + /* Controller/driver for one 2-phase bipolar stepper motor */ + tmc51xx: tmc51xx@0 { + compatible = "adi,tmc51xx"; + reg = <0>; + spi-max-frequency = ; /* Maximum SPI bus frequency */ + + #address-cells = <1>; + #size-cells = <0>; + + en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */ + clock-frequency = ; /* Internal/External Clock frequency */ + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC51XX */ + activate-stallguard2; + stallguard-velocity-check-interval-ms=<100>; + stallguard2-threshold=<9>; + stallguard-threshold-velocity=<500000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + thigh = <90>; + tcoolthrs = <100>; + tpwmthrs = <110>; + tpowerdown = <120>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; + }; + }; + + +compatible: "adi,tmc51xx" + +include: + - name: spi-device.yaml + - name: adi,trinamic-gconf.yaml + property-allowlist: + - en-pwm-mode + - test-mode + - name: stepper-controller.yaml + - name: base.yaml + property-allowlist: + - reg + - name: adi,trinamic-ramp-generator.yaml + property-allowlist: + - vstart + - a1 + - v1 + - amax + - vmax + - dmax + - d1 + - vstop + - tzerowait + - thigh + - tcoolthrs + - tpwmthrs + - tpowerdown + - ihold + - irun + - iholddelay + - name: adi,trinamic-stallguard.yaml + property-allowlist: + - activate-stallguard2 + - stallguard2-threshold + - stallguard-threshold-velocity + - stallguard-velocity-check-interval-ms + +properties: + "#address-cells": + default: 1 + const: 1 + + "#size-cells": + default: 0 + const: 0 + + clock-frequency: + type: int + required: true + description: | + The frequency of the clock signal provided to the TMC51XX. + This is used for real world conversion. + + Hint: µstep velocity v[Hz] µsteps / s v[Hz] = v[51xx] * ( fCLK[Hz]/2 / 2^23 ) + where v[51xx] is the value written to the TMC51XX. diff --git a/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml b/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml index 906e863f84d..be25692bb0d 100644 --- a/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml +++ b/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S # SPDX-License-Identifier: Apache-2.0 description: Global configuration flags for Trinamic stepper controller. @@ -72,3 +73,11 @@ properties: type: boolean description: | 1: GCONF is locked against further write access. + + en-pwm-mode: + type: boolean + description: | + 1: StealthChop voltage PWM mode enabled + (depending on velocity thresholds). Switch from + off to on state while in stand-still and at IHOLD= + nominal IRUN current, only. diff --git a/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml b/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml index 42d9b9e41fb..381ddf9a654 100644 --- a/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml +++ b/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml @@ -1,4 +1,5 @@ # SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S # SPDX-License-Identifier: Apache-2.0 description: Ramp Generator Motion Control Register-Set for Trinamic stepper controller. @@ -131,3 +132,63 @@ properties: (constant off time with slow decay, only). - If vhighfs is set, the motor operates in fullstep mode. - Voltage PWM mode StealthChop is switched off, if configured + + tcoolthrs: + type: int + default: 0 + description: | + This is the lower threshold velocity for switching on smart + energy CoolStep and StallGuard feature. (unsigned) + Set this parameter to disable CoolStep at low speeds, where it + cannot work reliably. The stop on stall function (enable with + sg_stop when using internal motion controller) and the stall + output signal become enabled when exceeding this velocity. In + non-DcStep mode, it becomes disabled again once the velocity + falls below this threshold. + TCOOLTHRS ≥ TSTEP ≥ THIGH: + - CoolStep is enabled, if configured + - StealthChop voltage PWM mode is disabled + TCOOLTHRS ≥ TSTEP + - Stop on stall is enabled, if configured + - Stall output signal (DIAG0/1) is enabled, if configured + + thigh: + type: int + default: 0 + description: | + This velocity setting allows velocity dependent switching into + a different chopper mode and fullstepping to maximize torque. + (unsigned) + The stall detection feature becomes switched off for 2-3 + electrical periods whenever passing THIGH threshold to + compensate for the effect of switching modes. + TSTEP ≤ THIGH: + - CoolStep is disabled (motor runs with normal current + scale) + - StealthChop voltage PWM mode is disabled + - If vhighchm is set, the chopper switches to chm=1 + with TFD=0 (constant off time with slow decay, only). + - If vhighfs is set, the motor operates in fullstep mode, + and the stall detection becomes switched over to + DcStep stall detection. + + tpwmthrs: + type: int + default: 0 + description: | + This is the upper velocity for StealthChop voltage PWM mode. + TSTEP ≥ TPWMTHRS + - StealthChop PWM mode is enabled, if configured + - DcStep is disabled + + tpowerdown: + type: int + default: 10 + description: | + TPOWERDOWN sets the delay time after stand still (stst) of the + motor to motor current power down. Time range is about 0 to + 4 seconds. + Attention: A minimum setting of 2 is required to allow + automatic tuning of StealthChop PWM_OFS_AUTO. + Reset Default = 10 + 0…((2^8)-1) * 2^18 tCLK diff --git a/tests/drivers/build_all/stepper/spi.dtsi b/tests/drivers/build_all/stepper/spi.dtsi index 98f80ceb944..1999e3ee017 100644 --- a/tests/drivers/build_all/stepper/spi.dtsi +++ b/tests/drivers/build_all/stepper/spi.dtsi @@ -8,19 +8,115 @@ adi_tmc50xx: adi_tmc50xx@0 { compatible = "adi,tmc50xx"; + status = "okay"; reg = <0x0>; - spi-max-frequency = <0>; + spi-max-frequency = <8000000>; + label = "tmc5041_0"; + #address-cells = <1>; #size-cells = <0>; - clock-frequency = <1>; + + poscmp-enable; test-mode; lock-gconf; /* ADI TMC Global configuration flags */ + clock-frequency = <16000000>; /* Internal/External Clock frequency */ tmc50xx_0: tmc50xx_0@0 { status = "okay"; reg = <0>; + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC50XX */ + activate-stallguard2; + stallguard-velocity-check-interval-ms=<100>; + stallguard2-threshold=<9>; + stallguard-threshold-velocity=<500000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + vhigh = <90>; + vcoolthrs = <100>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; }; tmc50xx_1: tmc50xx_1@1 { status = "okay"; reg = <1>; + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC50XX */ + activate-stallguard2; + stallguard-velocity-check-interval-ms=<100>; + stallguard2-threshold=<9>; + stallguard-threshold-velocity=<500000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + vhigh = <90>; + vcoolthrs = <100>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; }; }; + +adi_tmc51xx: adi_tmc51xx@1 { + compatible = "adi,tmc51xx"; + status = "okay"; + reg = <0x01>; + spi-max-frequency = <8000000>; + label = "tmc5160_1"; + + #address-cells = <1>; + #size-cells = <0>; + + en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */ + clock-frequency = <16000000>; /* Internal/External Clock frequency */ + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC5160 */ + activate-stallguard2; + stallguard-velocity-check-interval-ms=<100>; + stallguard2-threshold=<9>; + stallguard-threshold-velocity=<50000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + thigh = <90>; + tcoolthrs = <100>; + tpwmthrs = <110>; + tpowerdown = <120>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; +};