diff --git a/drivers/stepper/adi_tmc/CMakeLists.txt b/drivers/stepper/adi_tmc/CMakeLists.txt index a261deef90802..9412fb506a0a2 100644 --- a/drivers/stepper/adi_tmc/CMakeLists.txt +++ b/drivers/stepper/adi_tmc/CMakeLists.txt @@ -6,3 +6,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_TMC5041 adi_tmc5041_stepper_controller.c) +zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC51XX adi_tmc51xx_stepper_controller.c) diff --git a/drivers/stepper/adi_tmc/Kconfig b/drivers/stepper/adi_tmc/Kconfig index 7a88ec598fe17..ce1abdba16652 100644 --- a/drivers/stepper/adi_tmc/Kconfig +++ b/drivers/stepper/adi_tmc/Kconfig @@ -52,4 +52,30 @@ config STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC help The interval in ms to poll the ramp status on TMC5041. +config STEPPER_ADI_TMC51XX + bool "Activate trinamic tmc5130/tmc5160 stepper driver" + depends on DT_HAS_ADI_TMC51XX_ENABLED && STEPPER_ADI_TMC + select STEPPER_ADI_TMC_SPI + default y + help + Stepper driver for TMC5130/TMC5160. + +config STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + bool "TMC51XX poll ramp status" + depends on STEPPER_ADI_TMC51XX + default y + help + When enabled, the ramp status will be polled on TMC51XX, to check for events: + - TMC51XX_POS_REACHED_EVENT + - TMC51XX_STOP_SG_EVENT + - TMC51XX_STOP_LEFT_EVENT + - TMC51XX_STOP_RIGHT_EVENT + +config STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC + int "TMC51XX poll ramp status interval in ms" + depends on STEPPER_ADI_TMC51XX_RAMPSTAT_POLL + default 100 + help + The interval in ms to poll the ramp status on TMC51XX. + endif # STEPPER_ADI_TMC 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 0000000000000..2bcc972c5fa53 --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc51xx_stepper_controller.c @@ -0,0 +1,715 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Stefano Cottafavi + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_tmc51xx + +#include + +#include +#include + +#include "adi_tmc_reg.h" +#include "adi_tmc_spi.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; + /* StallGuard */ + const int8_t sg_threshold; + const bool is_sg_enabled; + const uint32_t sg_velocity_check_interval_ms; + const uint32_t sg_threshold_velocity; + /* Ramp */ +#ifdef CONFIG_STEPPER_ADI_TMC_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, TMC51XX_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, TMC51XX_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 void calculate_velocity_from_hz_to_fclk(const struct device *dev, const uint32_t velocity_hz, + uint32_t *const velocity_fclk) +{ + const struct tmc51xx_config *config = dev->config; + + *velocity_fclk = + ((uint64_t)(velocity_hz) << TMC51XX_CLOCK_FREQ_SHIFT) / config->clock_frequency; + LOG_DBG("Stepper motor controller %s velocity: %d Hz, velocity_fclk: %d", dev->name, + velocity_hz, *velocity_fclk); +} + +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 |= TMC51XX_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 &= ~TMC51XX_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); + int err; + + const struct tmc51xx_config *stepper_config = stepper_data->stepper->config; + + err = stallguard_enable(stepper_data->stepper, true); + if (err == -EAGAIN) { + LOG_ERR("retrying stallguard activation"); + k_work_reschedule(dwork, K_MSEC(stepper_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); + + uint32_t drv_status; + int err; + + tmc51xx_read(stepper_data->stepper, TMC51XX_DRVSTATUS, &drv_status); + + LOG_DBG("ramp workhandler"); + + if (FIELD_GET(TMC51XX_DRV_STATUS_SG_STATUS_MASK, drv_status) == 1U) { + LOG_INF("%s: Stall detected", stepper_data->stepper->name); + err = tmc51xx_write(stepper_data->stepper, TMC51XX_RAMPMODE, + TMC51XX_RAMPMODE_HOLD_MODE); + if (err != 0) { + LOG_ERR("%s: Failed to stop motor", stepper_data->stepper->name); + return; + } + } + + uint32_t rampstat_value; + + err = tmc51xx_read(stepper_data->stepper, TMC51XX_RAMPSTAT, &rampstat_value); + if (err != 0) { + LOG_ERR("%s: Failed to read RAMPSTAT register", stepper_data->stepper->name); + return; + } + + const uint8_t ramp_stat_values = FIELD_GET(TMC51XX_RAMPSTAT_INT_MASK, rampstat_value); + + if (ramp_stat_values > 0) { + switch (ramp_stat_values) { + + case TMC51XX_STOP_LEFT_EVENT: + LOG_DBG("RAMPSTAT %s:Left end-stop detected", stepper_data->stepper->name); + execute_callback(stepper_data->stepper, + STEPPER_EVENT_LEFT_END_STOP_DETECTED); + break; + + case TMC51XX_STOP_RIGHT_EVENT: + LOG_DBG("RAMPSTAT %s:Right end-stop detected", stepper_data->stepper->name); + execute_callback(stepper_data->stepper, + STEPPER_EVENT_RIGHT_END_STOP_DETECTED); + break; + + case TMC51XX_POS_REACHED_EVENT: + LOG_DBG("RAMPSTAT %s:Position reached", stepper_data->stepper->name); + execute_callback(stepper_data->stepper, STEPPER_EVENT_STEPS_COMPLETED); + break; + + case TMC51XX_STOP_SG_EVENT: + LOG_DBG("RAMPSTAT %s:Stall detected", stepper_data->stepper->name); + stallguard_enable(stepper_data->stepper, false); + execute_callback(stepper_data->stepper, 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, const bool enable) +{ + LOG_DBG("Stepper motor controller %s %s", dev->name, enable ? "enabled" : "disabled"); + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + + if (enable) { + reg_value |= TMC51XX_CHOPCONF_DRV_ENABLE_MASK; + } else { + reg_value &= ~TMC51XX_CHOPCONF_DRV_ENABLE_MASK; + } + + err = tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); + if (err != 0) { + return -EIO; + } + return 0; +} + +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(TMC51XX_DRV_STATUS_STST_BIT, reg_value) != 1U); + LOG_DBG("Stepper motor controller %s is moving: %d", dev->name, *is_moving); + return 0; +} + +static int tmc51xx_stepper_move(const struct device *dev, const int32_t steps) +{ + 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; + } + } + + int32_t position; + + err = stepper_get_actual_position(dev, &position); + if (err != 0) { + return -EIO; + } + int32_t target_position = position + steps; + + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC51XX_RAMPMODE_POSITIONING_MODE); + if (err != 0) { + return -EIO; + } + LOG_DBG("Stepper motor controller %s moved to %d by steps: %d", dev->name, target_position, + steps); + err = tmc51xx_write(dev, TMC51XX_XTARGET, target_position); + 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_set_max_velocity(const struct device *dev, uint32_t velocity) +{ + uint32_t velocity_fclk; + int err; + + calculate_velocity_from_hz_to_fclk(dev, velocity, &velocity_fclk); + 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) +{ + uint32_t reg_value; + int err; + + err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + if (err != 0) { + return -EIO; + } + + reg_value &= ~TMC51XX_CHOPCONF_MRES_MASK; + reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) + << TMC51XX_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 &= TMC51XX_CHOPCONF_MRES_MASK; + reg_value >>= TMC51XX_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_actual_position(const struct device *dev, const int32_t position) +{ + int err; + + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC51XX_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_set_target_position(const struct device *dev, const int32_t position) +{ + LOG_DBG("Stepper motor controller %s set target position to %d", dev->name, position); + 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, TMC51XX_RAMPMODE_POSITIONING_MODE); + if (err != 0) { + return -EIO; + } + tmc51xx_write(dev, TMC51XX_XTARGET, position); + + 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_enable_constant_velocity_mode(const struct device *dev, + const enum stepper_direction direction, + const uint32_t velocity) +{ + LOG_DBG("Stepper motor controller %s enable constant velocity mode", dev->name); + const struct tmc51xx_config *config = dev->config; + struct tmc51xx_data *data = dev->data; + uint32_t velocity_fclk; + int err; + + calculate_velocity_from_hz_to_fclk(dev, velocity, &velocity_fclk); + + 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, TMC51XX_RAMPMODE_POSITIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_VMAX, velocity_fclk); + if (err != 0) { + return -EIO; + } + break; + + case STEPPER_DIRECTION_NEGATIVE: + err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC51XX_RAMPMODE_NEGATIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_VMAX, velocity_fclk); + 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_TMC_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->vhigh); + if (err != 0) { + return -EIO; + } + err = tmc51xx_write(dev, TMC51XX_TCOOLTHRS, ramp_data->vcoolthrs); + 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; + } + + /* Init configuration register */ + err = tmc51xx_write(dev, TMC51XX_GCONF, config->gconf); + if (err != 0) { + return -EIO; + } + + /* Re-write GSTAT to clear (error) status flags */ + err = tmc51xx_write(dev, TMC51XX_GSTAT, 0x07); + if (err != 0) { + return -EIO; + } + + err = tmc51xx_stepper_set_micro_step_res(dev, config->default_micro_step_res); + 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, TMC51XX_SG_MIN_VALUE, TMC51XX_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 + << TMC51XX_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_TMC_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 + + LOG_DBG("Device %s initialized", dev->name); + + return 0; +} + +#define TMC_RAMP_DT_SPEC_GET(inst) \ + { \ + .vstart = DT_INST_PROP(inst, vstart), .v1 = DT_INST_PROP(inst, v1), \ + .vmax = DT_INST_PROP(inst, vmax), .a1 = DT_INST_PROP(inst, a1), \ + .amax = DT_INST_PROP(inst, amax), .d1 = DT_INST_PROP(inst, d1), \ + .dmax = DT_INST_PROP(inst, dmax), .vstop = DT_INST_PROP(inst, vstop), \ + .tzerowait = DT_INST_PROP(inst, tzerowait), \ + .vcoolthrs = DT_INST_PROP(inst, vcoolthrs), .vhigh = DT_INST_PROP(inst, vhigh), \ + .iholdrun = (TMC51XX_IRUN(DT_INST_PROP(inst, irun)) | \ + TMC51XX_IHOLD(DT_INST_PROP(inst, ihold)) | \ + TMC51XX_IHOLDDELAY(DT_INST_PROP(inst, iholddelay))), \ + } + +#define TMC51XX_STEPPER_API_DEFINE(inst) \ + static const struct stepper_driver_api tmc51xx_api_##inst = { \ + .enable = tmc51xx_stepper_enable, \ + .is_moving = tmc51xx_stepper_is_moving, \ + .move = tmc51xx_stepper_move, \ + .set_max_velocity = tmc51xx_stepper_set_max_velocity, \ + .set_micro_step_res = tmc51xx_stepper_set_micro_step_res, \ + .get_micro_step_res = tmc51xx_stepper_get_micro_step_res, \ + .set_actual_position = tmc51xx_stepper_set_actual_position, \ + .get_actual_position = tmc51xx_stepper_get_actual_position, \ + .set_target_position = tmc51xx_stepper_set_target_position, \ + .enable_constant_velocity_mode = tmc51xx_stepper_enable_constant_velocity_mode, \ + .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"); \ + COND_CODE_1(DT_INST_PROP_EXISTS(inst, stallguard_threshold_velocity), \ + BUILD_ASSERT(DT_INST_PROP(inst, stallguard_threshold_velocity), \ + "stallguard threshold velocity must be a positive value"), ()); \ + IF_ENABLED(CONFIG_STEPPER_ADI_TMC_RAMP_GEN, (CHECK_RAMP_DT_DATA(inst))); \ + static struct tmc51xx_data tmc51xx_data_##inst = { \ + .stepper = DEVICE_DT_INST_GET(inst), \ + }; \ + static const struct tmc51xx_config tmc51xx_config_##inst = { \ + .gconf = ((DT_INST_PROP(inst, invert_direction) << TMC51XX_GCONF_SHAFT_SHIFT) | \ + (DT_INST_PROP(inst, test_mode) << TMC51XX_GCONF_TEST_MODE_SHIFT)), \ + .default_micro_step_res = DT_INST_PROP(inst, micro_step_res), \ + .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), \ + .is_sg_enabled = DT_INST_PROP(inst, activate_stallguard2), \ + COND_CODE_1(DT_INST_PROP(inst, activate_stallguard2), ( \ + .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),), ()) IF_ENABLED(CONFIG_STEPPER_ADI_TMC_RAMP_GEN, \ + (.default_ramp_config = TMC_RAMP_DT_SPEC_GET(inst))) }; \ + TMC51XX_STEPPER_API_DEFINE(inst) \ + DEVICE_DT_INST_DEFINE(inst, tmc51xx_init, NULL, &tmc51xx_data_##inst, \ + &tmc51xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \ + &tmc51xx_api_##inst); + +DT_INST_FOREACH_STATUS_OKAY(TMC51XX_DEFINE) diff --git a/drivers/stepper/adi_tmc/adi_tmc_reg.h b/drivers/stepper/adi_tmc/adi_tmc_reg.h index 1c88020c352a4..0d0c219d6301b 100644 --- a/drivers/stepper/adi_tmc/adi_tmc_reg.h +++ b/drivers/stepper/adi_tmc/adi_tmc_reg.h @@ -97,7 +97,7 @@ extern "C" { (TMC5041_RAMPSTAT_POS_REACHED_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) #define TMC5041_RAMPSTAT_STOP_SG_EVENT_MASK BIT(6) -#define TMC5041_STOP_SG_EVENT (TMC5041_RAMPSTAT_STOP_SG_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) +#define TMC5041_STOP_SG_EVENT (TMC5041_RAMPSTAT_STOP_SG_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) #define TMC5041_RAMPSTAT_STOP_RIGHT_EVENT_MASK BIT(5) #define TMC5041_STOP_RIGHT_EVENT \ @@ -137,6 +137,144 @@ extern "C" { #endif +#ifdef CONFIG_STEPPER_ADI_TMC51XX + +/** + * @name TMC51XX module registers + * @anchor TMC51XX_REGISTERS + * + * @{ + */ + +#define TMC51XX_GCONF_TEST_MODE_SHIFT 17 +#define TMC51XX_GCONF_SHAFT_SHIFT 4 + +#define TMC51XX_DRV_STATUS_STST_BIT BIT(31) + +#define TMC51XX_WRITE_BIT 0x80U +#define TMC51XX_ADDRESS_MASK 0x7FU + +#define TMC51XX_GCONF 0x00 +#define TMC51XX_GSTAT 0x01 +#define TMC51XX_IFCNT 0x02 +#define TMC51XX_SLAVECONF 0x03 +#define TMC51XX_INP_OUT 0x04 +#define TMC51XX_X_COMPARE 0x05 +#define TMC51XX_OTP_PROG 0x06 +#define TMC51XX_OTP_READ 0x07 +#define TMC51XX_FACTORY_CONF 0x08 +#define TMC51XX_SHORT_CONF 0x09 +#define TMC51XX_DRV_CONF 0x0A +#define TMC51XX_GLOBAL_SCALER 0x0B +#define TMC51XX_OFFSET_READ 0x0C +#define TMC51XX_IHOLD_IRUN 0x10 +#define TMC51XX_TPOWERDOWN 0x11 +#define TMC51XX_TSTEP 0x12 +#define TMC51XX_TPWMTHRS 0x13 +#define TMC51XX_TCOOLTHRS 0x14 +#define TMC51XX_THIGH 0x15 + +#define TMC51XX_RAMPMODE 0x20 +#define TMC51XX_XACTUAL 0x21 +#define TMC51XX_VACTUAL 0x22 +#define TMC51XX_VSTART 0x23 +#define TMC51XX_A1 0x24 +#define TMC51XX_V1 0x25 +#define TMC51XX_AMAX 0x26 +#define TMC51XX_VMAX 0x27 +#define TMC51XX_DMAX 0x28 +#define TMC51XX_D1 0x2A +#define TMC51XX_VSTOP 0x2B +#define TMC51XX_TZEROWAIT 0x2C +#define TMC51XX_XTARGET 0x2D + +#define TMC51XX_VDCMIN 0x33 +#define TMC51XX_SWMODE 0x34 +#define TMC51XX_RAMPSTAT 0x35 +#define TMC51XX_XLATCH 0x36 +#define TMC51XX_ENCMODE 0x38 +#define TMC51XX_XENC 0x39 +#define TMC51XX_ENC_CONST 0x3A +#define TMC51XX_ENC_STATUS 0x3B +#define TMC51XX_ENC_LATCH 0x3C +#define TMC51XX_ENC_DEVIATION 0x3D + +#define TMC51XX_MSLUT0 0x60 +#define TMC51XX_MSLUT1 0x61 +#define TMC51XX_MSLUT2 0x62 +#define TMC51XX_MSLUT3 0x63 +#define TMC51XX_MSLUT4 0x64 +#define TMC51XX_MSLUT5 0x65 +#define TMC51XX_MSLUT6 0x66 +#define TMC51XX_MSLUT7 0x67 +#define TMC51XX_MSLUTSEL 0x68 +#define TMC51XX_MSLUTSTART 0x69 +#define TMC51XX_MSCNT 0x6A +#define TMC51XX_MSCURACT 0x6B +#define TMC51XX_CHOPCONF 0x6C +#define TMC51XX_COOLCONF 0x6D +#define TMC51XX_DCCTRL 0x6E +#define TMC51XX_DRVSTATUS 0x6F +#define TMC51XX_PWMCONF 0x70 +#define TMC51XX_PWMSCALE 0x71 +#define TMC51XX_PWM_AUTO 0x72 +#define TMC51XX_LOST_STEPS 0x73 + +#define TMC51XX_RAMPMODE_POSITIONING_MODE 0 +#define TMC51XX_RAMPMODE_POSITIVE_VELOCITY_MODE 1 +#define TMC51XX_RAMPMODE_NEGATIVE_VELOCITY_MODE 2 +#define TMC51XX_RAMPMODE_HOLD_MODE 3 + +#define TMC51XX_SW_MODE_SG_STOP_ENABLE BIT(10) + +#define TMC51XX_RAMPSTAT_INT_MASK GENMASK(7, 4) +#define TMC51XX_RAMPSTAT_INT_SHIFT 4 + +#define TMC51XX_RAMPSTAT_POS_REACHED_EVENT_MASK BIT(7) +#define TMC51XX_POS_REACHED_EVENT \ + (TMC51XX_RAMPSTAT_POS_REACHED_EVENT_MASK >> TMC51XX_RAMPSTAT_INT_SHIFT) + +#define TMC51XX_RAMPSTAT_STOP_SG_EVENT_MASK BIT(6) +#define TMC51XX_STOP_SG_EVENT (TMC51XX_RAMPSTAT_STOP_SG_EVENT_MASK >> TMC51XX_RAMPSTAT_INT_SHIFT) + +#define TMC51XX_RAMPSTAT_STOP_RIGHT_EVENT_MASK BIT(5) +#define TMC51XX_STOP_RIGHT_EVENT \ + (TMC51XX_RAMPSTAT_STOP_RIGHT_EVENT_MASK >> TMC51XX_RAMPSTAT_INT_SHIFT) + +#define TMC51XX_RAMPSTAT_STOP_LEFT_EVENT_MASK BIT(4) +#define TMC51XX_STOP_LEFT_EVENT \ + (TMC51XX_RAMPSTAT_STOP_LEFT_EVENT_MASK >> TMC51XX_RAMPSTAT_INT_SHIFT) + +#define TMC51XX_DRV_STATUS_STST_BIT BIT(31) +#define TMC51XX_DRV_STATUS_SG_RESULT_MASK GENMASK(9, 0) +#define TMC51XX_DRV_STATUS_SG_STATUS_MASK BIT(24) +#define TMC51XX_DRV_STATUS_SG_STATUS_SHIFT 24 + +#define TMC51XX_SG_MIN_VALUE -64 +#define TMC51XX_SG_MAX_VALUE 63 + +#define TMC51XX_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT 16 + +#define TMC51XX_IHOLD_MASK GENMASK(4, 0) +#define TMC51XX_IHOLD_SHIFT 0 +#define TMC51XX_IHOLD(n) (((n) << TMC51XX_IHOLD_SHIFT) & TMC51XX_IHOLD_MASK) + +#define TMC51XX_IRUN_MASK GENMASK(12, 8) +#define TMC51XX_IRUN_SHIFT 8 +#define TMC51XX_IRUN(n) (((n) << TMC51XX_IRUN_SHIFT) & TMC51XX_IRUN_MASK) + +#define TMC51XX_IHOLDDELAY_MASK GENMASK(19, 16) +#define TMC51XX_IHOLDDELAY_SHIFT 16 +#define TMC51XX_IHOLDDELAY(n) (((n) << TMC51XX_IHOLDDELAY_SHIFT) & TMC51XX_IHOLDDELAY_MASK) + +#define TMC51XX_CHOPCONF_DRV_ENABLE_MASK GENMASK(3, 0) +#define TMC51XX_CHOPCONF_MRES_MASK GENMASK(27, 24) +#define TMC51XX_CHOPCONF_MRES_SHIFT 24 + +#define TMC51XX_CLOCK_FREQ_SHIFT 24 + +#endif + /** * @} */ diff --git a/drivers/stepper/adi_tmc/adi_tmc_spi.c b/drivers/stepper/adi_tmc/adi_tmc_spi.c index 7aa3bed69d56f..fd8d589ff07d0 100644 --- a/drivers/stepper/adi_tmc/adi_tmc_spi.c +++ b/drivers/stepper/adi_tmc/adi_tmc_spi.c @@ -19,11 +19,13 @@ static void parse_tmc_spi_status(const uint8_t status_byte) LOG_WRN("spi dataframe: reset_flag detected"); } if ((status_byte & BIT_MASK(1)) != 0) { - LOG_WRN("spi dataframe: driver_error(1) detected"); + LOG_WRN("spi dataframe: driver_error detected"); } +#ifdef CONFIG_STEPPER_ADI_TMC5041 if ((status_byte & BIT_MASK(2)) != 0) { LOG_WRN("spi dataframe: driver_error(2) detected"); } +#endif } static void print_tx_rx_buffer(const uint8_t *const tx_buffer, const uint8_t *const rx_buffer) diff --git a/dts/bindings/stepper/adi/adi,tmc51xx.yaml b/dts/bindings/stepper/adi/adi,tmc51xx.yaml new file mode 100644 index 0000000000000..ed2d80401d81a --- /dev/null +++ b/dts/bindings/stepper/adi/adi,tmc51xx.yaml @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Stefano Cottafavi +# SPDX-License-Identifier: Apache-2.0 + +description: | + Analog Devices TMC5130/TMC5160 Stepper Motor Controller + + Example: + + &spi0 { + /* SPI bus options here, not shown */ + + tmc5160: tmc5160@0 { + compatible = "adi,tmc51xx"; + reg = <0>; + spi-max-frequency = ; /* Maximum SPI bus frequency */ + + #address-cells = <1>; + #size-cells = <0>; + + clock-frequency = ; /* Internal/External Clock frequency */ + + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC5041 */ + 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>; + }; + }; + + +compatible: "adi,tmc51xx" + +include: + - name: spi-device.yaml + - name: adi,trinamic-gconf.yaml + property-allowlist: + - shaft + - test_mode + - name: stepper-controller.yaml + property-allowlist: + - invert-direction + - micro-step-res + - name: adi,trinamic-ramp-generator.yaml + property-allowlist: + - vstart + - a1 + - v1 + - amax + - vmax + - dmax + - d1 + - vstop + - tzerowait + - vhigh + - vcoolthrs + - 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 TMC5160. + This is used for real world conversion. + + Hint: µstep velocity v[Hz] µsteps / s v[Hz] = v[5160] * ( fCLK[Hz]/2 / 2^23 ) + where v[5160] is the value written to the TMC5160. diff --git a/include/zephyr/drivers/stepper/stepper_trinamic.h b/include/zephyr/drivers/stepper/stepper_trinamic.h index d36707c14b0b0..ce759d48f87be 100644 --- a/include/zephyr/drivers/stepper/stepper_trinamic.h +++ b/include/zephyr/drivers/stepper/stepper_trinamic.h @@ -131,6 +131,7 @@ struct tmc_ramp_generator_data { * * @return struct tmc_ramp_generator_data */ +#ifdef CONFIG_STEPPER_ADI_TMC5041 #define TMC_RAMP_DT_SPEC_GET(node) \ { \ .vstart = DT_PROP(node, vstart), \ @@ -148,6 +149,7 @@ struct tmc_ramp_generator_data { TMC5041_IHOLD(DT_PROP(node, ihold)) | \ TMC5041_IHOLDDELAY(DT_PROP(node, iholddelay))), \ } +#endif /** * @brief Configure Trinamic Stepper Ramp Generator @@ -159,8 +161,10 @@ struct tmc_ramp_generator_data { * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ +#ifdef CONFIG_STEPPER_ADI_TMC5041 int tmc5041_stepper_set_ramp(const struct device *dev, const struct tmc_ramp_generator_data *ramp_data); +#endif /** * @} diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index 63e7fb948b73c..0a0b906d796d9 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -285,7 +285,7 @@ class ClangFormatCheck(ComplianceTest): path_hint = "" def run(self): - exe = f"clang-format-diff.{'exe' if platform.system() == 'Windows' else 'py'}" + exe = f"clang-format-diff{'exe' if platform.system() == 'Windows' else ''}" for file in get_files(): if Path(file).suffix not in ['.c', '.h']: