diff --git a/drivers/pwm/CMakeLists.txt b/drivers/pwm/CMakeLists.txt index 981428ff314..4f2fde5fd4f 100644 --- a/drivers/pwm/CMakeLists.txt +++ b/drivers/pwm/CMakeLists.txt @@ -29,6 +29,7 @@ zephyr_library_sources_ifdef(CONFIG_PWM_GECKO pwm_gecko.c) zephyr_library_sources_ifdef(CONFIG_PWM_GD32 pwm_gd32.c) zephyr_library_sources_ifdef(CONFIG_PWM_RCAR pwm_rcar.c) zephyr_library_sources_ifdef(CONFIG_PWM_PCA9685 pwm_pca9685.c) +zephyr_library_sources_ifdef(CONFIG_PWM_MAX31790 pwm_max31790.c) zephyr_library_sources_ifdef(CONFIG_PWM_TEST pwm_test.c) zephyr_library_sources_ifdef(CONFIG_PWM_RPI_PICO pwm_rpi_pico.c) zephyr_library_sources_ifdef(CONFIG_PWM_BBLED_XEC pwm_mchp_xec_bbled.c) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index f205dbb525a..796989048f0 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -81,6 +81,8 @@ source "drivers/pwm/Kconfig.rcar" source "drivers/pwm/Kconfig.pca9685" +source "drivers/pwm/Kconfig.max31790" + source "drivers/pwm/Kconfig.test" source "drivers/pwm/Kconfig.rpi_pico" diff --git a/drivers/pwm/Kconfig.max31790 b/drivers/pwm/Kconfig.max31790 new file mode 100644 index 00000000000..f513d789277 --- /dev/null +++ b/drivers/pwm/Kconfig.max31790 @@ -0,0 +1,13 @@ +# +# Copyright (c) 2023 SILA Embedded Solutions GmbH +# +# SPDX-License-Identifier: Apache-2.0 +# + +config PWM_MAX31790 + bool "MAX31790 6-channel I2C-bus PWM controller" + default y + depends on DT_HAS_MAXIM_MAX31790_ENABLED + select I2C + help + Enable driver for MAX31790 6-channel I2C-bus PWM controller. diff --git a/drivers/pwm/pwm_max31790.c b/drivers/pwm/pwm_max31790.c new file mode 100644 index 00000000000..dab9ee58159 --- /dev/null +++ b/drivers/pwm/pwm_max31790.c @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2023 SILA Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT maxim_max31790 + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(pwm_max31790, CONFIG_PWM_LOG_LEVEL); + +#define MAX31790_OSCILLATOR_FREQUENCY_IN_HZ 32768 +#define MAX31790_PWMTARGETDUTYCYCLE_MAXIMUM ((1 << 9) - 1) +#define MAX31790_CHANNEL_COUNT 6 + +struct max31790_config { + struct i2c_dt_spec i2c; +}; + +struct max31790_data { + struct k_mutex lock; +}; + +#define MAX37190_REGISTER_GLOBALCONFIGURATION 0x00 +#define MAX37190_REGISTER_PWMFREQUENCY 0x01 +#define MAX37190_REGISTER_FANCONFIGURATION(channel) (0x02 + channel) +#define MAX31790_REGISTER_PWMOUTTARGETDUTYCYCLEMSB(channel) (0x40 + 2 * channel) +#define MAX31790_REGISTER_FANDYNAMICS(channel) (0x08 + channel) +#define MAX31790_REGISTER_TACHTARGETCOUNTMSB(channel) (0x50 + 2 * channel) + +#define MAX37190_GLOBALCONFIGURATION_STANDBY_BIT BIT(7) +#define MAX37190_FANXCONFIGURATION_MONITOR_BIT BIT(4) +#define MAX37190_FANXCONFIGURATION_TACHINPUTENABLED_BIT BIT(3) +#define MAX37190_FANXCONFIGURATION_LOCKEDROTOR_BIT BIT(2) +#define MAX37190_FANXCONFIGURATION_LOCKEDROTORPOLARITY_BIT BIT(1) +#define MAX37190_FANXCONFIGURATION_TACH_BIT BIT(0) +#define MAX37190_FANXCONFIGURATION_MODE_BIT BIT(7) +#define MAX37190_FANXDYNAMICS_ASYMMETRICRATEOFCHANGE_BIT BIT(1) + +#define MAX37190_FANXDYNAMICS_SPEEDRANGE_LENGTH 3 +#define MAX37190_FANXDYNAMICS_SPEEDRANGE_POS 5 +#define MAX37190_FANXDYNAMICS_PWMRATEOFCHANGE_LENGTH 3 +#define MAX37190_FANXDYNAMICS_PWMRATEOFCHANGE_POS 2 +#define MAX37190_PWMFREQUENCY_PWM_LENGTH 4 +#define MAX37190_PWMFREQUENCY_PWM4TO6_POS 4 +#define MAX37190_PWMFREQUENCY_PWM1TO3_LENGTH 4 +#define MAX37190_PWMFREQUENCY_PWM1TO3_POS 0 +#define MAX37190_FANXCONFIGURATION_SPINUP_LENGTH 2 +#define MAX37190_FANXCONFIGURATION_SPINUP_POS 5 + +#define PWM_MAX31790_FLAG_SPEED_RANGE_GET(flags) \ + FIELD_GET(GENMASK(MAX37190_FANXDYNAMICS_SPEEDRANGE_LENGTH + \ + PWM_MAX31790_FLAG_SPEED_RANGE_POS - 1, \ + PWM_MAX31790_FLAG_SPEED_RANGE_POS), \ + flags) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_GET(flags) \ + FIELD_GET(GENMASK(MAX37190_FANXDYNAMICS_PWMRATEOFCHANGE_LENGTH + \ + PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS - 1, \ + PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS), \ + flags) + +static void max31790_set_fandynamics_speedrange(uint8_t *destination, uint8_t value) +{ + uint8_t length = MAX37190_FANXDYNAMICS_SPEEDRANGE_LENGTH; + uint8_t pos = MAX37190_FANXDYNAMICS_SPEEDRANGE_POS; + + *destination &= ~GENMASK(pos + length - 1, pos); + *destination |= FIELD_PREP(GENMASK(pos + length - 1, pos), value); +} + +static void max31790_set_fandynamics_pwmrateofchange(uint8_t *destination, uint8_t value) +{ + uint8_t length = MAX37190_FANXDYNAMICS_PWMRATEOFCHANGE_LENGTH; + uint8_t pos = MAX37190_FANXDYNAMICS_PWMRATEOFCHANGE_POS; + + *destination &= ~GENMASK(pos + length - 1, pos); + *destination |= FIELD_PREP(GENMASK(pos + length - 1, pos), value); +} + +static void max31790_set_pwmfrequency(uint8_t *destination, uint8_t channel, uint8_t value) +{ + uint8_t length = MAX37190_PWMFREQUENCY_PWM_LENGTH; + uint8_t pos = (channel / 3) * 4; + + *destination &= ~GENMASK(pos + length - 1, pos); + *destination |= FIELD_PREP(GENMASK(pos + length - 1, pos), value); +} + +static uint8_t max31790_get_pwmfrequency(uint8_t value, uint8_t channel) +{ + uint8_t length = MAX37190_PWMFREQUENCY_PWM_LENGTH; + uint8_t pos = (channel / 3) * 4; + + return FIELD_GET(GENMASK(pos + length - 1, pos), value); +} + +static void max31790_set_fanconfiguration_spinup(uint8_t *destination, uint8_t value) +{ + uint8_t length = MAX37190_FANXCONFIGURATION_SPINUP_LENGTH; + uint8_t pos = MAX37190_FANXCONFIGURATION_SPINUP_POS; + + *destination &= ~GENMASK(pos + length - 1, pos); + *destination |= FIELD_PREP(GENMASK(pos + length - 1, pos), value); +} + +static int max31790_read_register(const struct device *dev, uint8_t address, uint8_t *value) +{ + const struct max31790_config *config = dev->config; + int result; + + result = i2c_reg_read_byte_dt(&config->i2c, address, value); + if (result != 0) { + LOG_ERR("unable to read register 0x%02X, error %i", address, result); + } + + LOG_DBG("read value 0x%02X from register 0x%02X", *value, address); + + return result; +} + +static int max31790_write_register_uint8(const struct device *dev, uint8_t address, uint8_t value) +{ + const struct max31790_config *config = dev->config; + int result; + + LOG_DBG("writing value 0x%02X to register 0x%02X", value, address); + result = i2c_reg_write_byte_dt(&config->i2c, address, value); + if (result != 0) { + LOG_ERR("unable to write register 0x%02X, error %i", address, result); + } + + return result; +} + +static int max31790_write_register_uint16(const struct device *dev, uint8_t address, uint16_t value) +{ + const struct max31790_config *config = dev->config; + int result; + uint8_t buffer[] = { + address, + value >> 8, + value, + }; + + LOG_DBG("writing value 0x%04X to address 0x%02X", value, address); + result = i2c_write_dt(&config->i2c, buffer, sizeof(buffer)); + if (result != 0) { + LOG_ERR("unable to write to address 0x%02X, error %i", address, result); + } + + return result; +} + +static bool max31790_convert_pwm_frequency_into_hz(uint16_t *result, uint8_t pwm_frequency) +{ + switch (pwm_frequency) { + case 0: + *result = 25; + return true; + case 1: + *result = 30; + return true; + case 2: + *result = 35; + return true; + case 3: + *result = 100; + return true; + case 4: + *result = 125; + return true; + case 5: + *result = 150; /* actually 149.7, according to the datasheet */ + return true; + case 6: + *result = 1250; + return true; + case 7: + *result = 1470; + return true; + case 8: + *result = 3570; + return true; + case 9: + *result = 5000; + return true; + case 10: + *result = 12500; + return true; + case 11: + *result = 25000; + return true; + default: + LOG_ERR("invalid value %i for PWM frequency register", pwm_frequency); + return false; + } +} + +static bool max31790_convert_pwm_frequency_into_register(uint8_t *result, uint32_t pwm_frequency) +{ + switch (pwm_frequency) { + case 25: + *result = 0; + return true; + case 30: + *result = 1; + return true; + case 35: + *result = 2; + return true; + case 100: + *result = 3; + return true; + case 125: + *result = 4; + return true; + case 150: /* actually 149.7, according to the datasheet */ + *result = 5; + return true; + case 1250: + *result = 6; + return true; + case 1470: + *result = 7; + return true; + case 3570: + *result = 8; + return true; + case 5000: + *result = 9; + return true; + case 12500: + *result = 10; + return true; + case 25000: + *result = 11; + return true; + default: + LOG_ERR("invalid value %i for PWM frequency in Hz", pwm_frequency); + return false; + } +} + +static int max31790_set_cycles_internal(const struct device *dev, uint32_t channel, + uint32_t period_count, uint32_t pulse_count, + pwm_flags_t flags) +{ + int result; + uint8_t pwm_frequency_channel_value; + uint8_t value_pwm_frequency; + uint8_t value_fan_configuration; + uint8_t value_fan_dynamics; + uint8_t value_speed_range = PWM_MAX31790_FLAG_SPEED_RANGE_GET(flags); + uint8_t value_pwm_rate_of_change = PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_GET(flags); + + if (!max31790_convert_pwm_frequency_into_register(&pwm_frequency_channel_value, + period_count)) { + return -EINVAL; + } + + result = max31790_read_register(dev, MAX37190_REGISTER_PWMFREQUENCY, &value_pwm_frequency); + if (result != 0) { + return result; + } + + max31790_set_pwmfrequency(&value_pwm_frequency, channel, pwm_frequency_channel_value); + + result = max31790_write_register_uint8(dev, MAX37190_REGISTER_PWMFREQUENCY, + value_pwm_frequency); + if (result != 0) { + return result; + } + + value_fan_configuration = 0; + value_fan_dynamics = 0; + + if (flags & PWM_MAX31790_FLAG_SPIN_UP) { + max31790_set_fanconfiguration_spinup(&value_fan_configuration, 2); + } else { + max31790_set_fanconfiguration_spinup(&value_fan_configuration, 0); + } + + value_fan_configuration &= ~MAX37190_FANXCONFIGURATION_MONITOR_BIT; + value_fan_configuration &= ~MAX37190_FANXCONFIGURATION_LOCKEDROTOR_BIT; + value_fan_configuration &= ~MAX37190_FANXCONFIGURATION_LOCKEDROTORPOLARITY_BIT; + value_fan_configuration &= ~MAX37190_FANXCONFIGURATION_TACH_BIT; + value_fan_configuration |= MAX37190_FANXCONFIGURATION_TACHINPUTENABLED_BIT; + + max31790_set_fandynamics_speedrange(&value_fan_dynamics, value_speed_range); + max31790_set_fandynamics_pwmrateofchange(&value_fan_dynamics, value_pwm_rate_of_change); + value_fan_dynamics |= MAX37190_FANXDYNAMICS_ASYMMETRICRATEOFCHANGE_BIT; + + if ((flags & PWM_MAX31790_FLAG_RPM_MODE) == 0) { + LOG_DBG("PWM mode"); + uint16_t pwm_target_duty_cycle = + pulse_count * MAX31790_PWMTARGETDUTYCYCLE_MAXIMUM / period_count; + value_fan_configuration &= ~MAX37190_FANXCONFIGURATION_MODE_BIT; + + result = max31790_write_register_uint16( + dev, MAX31790_REGISTER_PWMOUTTARGETDUTYCYCLEMSB(channel), + pwm_target_duty_cycle); + if (result != 0) { + return result; + } + } else { + LOG_DBG("RPM mode"); + value_fan_configuration |= MAX37190_FANXCONFIGURATION_MODE_BIT; + + result = max31790_write_register_uint16( + dev, MAX31790_REGISTER_TACHTARGETCOUNTMSB(channel), pulse_count); + if (result != 0) { + return result; + } + } + + result = max31790_write_register_uint8(dev, MAX37190_REGISTER_FANCONFIGURATION(channel), + value_fan_configuration); + if (result != 0) { + return result; + } + + result = max31790_write_register_uint8(dev, MAX31790_REGISTER_FANDYNAMICS(channel), + value_fan_dynamics); + if (result != 0) { + return result; + } + + return 0; +} + +static int max31790_set_cycles(const struct device *dev, uint32_t channel, uint32_t period_count, + uint32_t pulse_count, pwm_flags_t flags) +{ + struct max31790_data *data = dev->data; + int result; + + LOG_DBG("set period %i with pulse %i for channel %i and flags 0x%04X", period_count, + pulse_count, channel, flags); + + if (channel > MAX31790_CHANNEL_COUNT) { + LOG_ERR("invalid channel number %i", channel); + return -EINVAL; + } + + if (period_count == 0) { + LOG_ERR("period count must be > 0"); + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + result = max31790_set_cycles_internal(dev, channel, period_count, pulse_count, flags); + k_mutex_unlock(&data->lock); + return result; +} + +static int max31790_get_cycles_per_sec(const struct device *dev, uint32_t channel, uint64_t *cycles) +{ + struct max31790_data *data = dev->data; + int result; + bool success; + uint8_t pwm_frequency_register; + uint8_t pwm_frequency = 1; + uint16_t pwm_frequency_in_hz; + + if (channel > MAX31790_CHANNEL_COUNT) { + LOG_ERR("invalid channel number %i", channel); + return -EINVAL; + } + + k_mutex_lock(&data->lock, K_FOREVER); + result = max31790_read_register(dev, MAX37190_REGISTER_GLOBALCONFIGURATION, + &pwm_frequency_register); + + if (result != 0) { + k_mutex_unlock(&data->lock); + return result; + } + + pwm_frequency = max31790_get_pwmfrequency(pwm_frequency_register, channel); + success = max31790_convert_pwm_frequency_into_hz(&pwm_frequency_in_hz, pwm_frequency); + + if (!success) { + k_mutex_unlock(&data->lock); + return -EINVAL; + } + + *cycles = pwm_frequency_in_hz; + + k_mutex_unlock(&data->lock); + return 0; +} + +static const struct pwm_driver_api max31790_api = { + .set_cycles = max31790_set_cycles, + .get_cycles_per_sec = max31790_get_cycles_per_sec, +}; + +static int max31790_init(const struct device *dev) +{ + const struct max31790_config *config = dev->config; + struct max31790_data *data = dev->data; + int result; + uint8_t reg_value; + + k_mutex_init(&data->lock); + + if (!i2c_is_ready_dt(&config->i2c)) { + LOG_ERR("I2C device not ready"); + return -ENODEV; + } + + result = max31790_read_register(dev, MAX37190_REGISTER_GLOBALCONFIGURATION, ®_value); + if (result != 0) { + return result; + } + + if ((reg_value & MAX37190_GLOBALCONFIGURATION_STANDBY_BIT) != 0) { + LOG_DBG("taking PWM controller out of standby"); + + reg_value &= ~MAX37190_GLOBALCONFIGURATION_STANDBY_BIT; + result = max31790_write_register_uint8(dev, MAX37190_REGISTER_GLOBALCONFIGURATION, + reg_value); + if (result != 0) { + return result; + } + + result = max31790_read_register(dev, MAX37190_REGISTER_GLOBALCONFIGURATION, + ®_value); + if (result != 0) { + return result; + } + + if ((reg_value & MAX37190_GLOBALCONFIGURATION_STANDBY_BIT) != 0) { + LOG_ERR("unable to take PWM controller out of standby"); + return -ENODEV; + } + } + + return 0; +} + +#define MAX31790_INIT(inst) \ + static const struct max31790_config max31790_##inst##_config = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + }; \ + \ + static struct max31790_data max31790_##inst##_data; \ + \ + DEVICE_DT_INST_DEFINE(inst, max31790_init, NULL, &max31790_##inst##_data, \ + &max31790_##inst##_config, POST_KERNEL, CONFIG_PWM_INIT_PRIORITY, \ + &max31790_api); + +DT_INST_FOREACH_STATUS_OKAY(MAX31790_INIT); diff --git a/include/zephyr/drivers/pwm/max31790.h b/include/zephyr/drivers/pwm/max31790.h new file mode 100644 index 00000000000..6117487baaf --- /dev/null +++ b/include/zephyr/drivers/pwm/max31790.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023 SILA Embedded Solutions GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_PWM_MAX31790_H_ +#define ZEPHYR_INCLUDE_DRIVERS_PWM_MAX31790_H_ + +/** + * @name custom PWM flags for MAX31790 + * These flags can be used with the PWM API in the upper 8 bits of pwm_flags_t + * They allow the usage of the RPM mode, which will cause the MAX31790 to + * measure the actual speed of the fan and automatically control it to the + * desired speed. + * @{ + */ +/** @cond INTERNAL_HIDDEN */ +#define PWM_MAX31790_FLAG_RPM_MODE_POS 8 +#define PWM_MAX31790_FLAG_SPEED_RANGE_POS 9 +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS 12 +#define PWM_MAX31790_FLAG_SPIN_UP_POS 15 +/** @endcond */ + +/*! + * @brief RPM mode + * + * Activating the RPM mode will cause the parameter pulse of @ref pwm_set_cycles + * to be interpreted as TACH target count. This basically is the number of internal + * pulses which occur during each TACH period. Hence, a bigger value means a slower + * rotation of the fan. The details about the TACH target count has to be calculated + * can be taken from the datasheet of the MAX31790. + */ +#define PWM_MAX31790_FLAG_RPM_MODE (1 << PWM_MAX31790_FLAG_RPM_MODE_POS) +/*! + * @brief speed range of fan + * + * This represents a multiplicator for the TACH count and should be chosen depending + * on the nominal RPM of the fan. A detailed table on how to choose a proper value + * can be found in the datasheet of the MAX31790. + */ +#define PWM_MAX31790_FLAG_SPEED_RANGE_1 (0 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +#define PWM_MAX31790_FLAG_SPEED_RANGE_2 (1 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +#define PWM_MAX31790_FLAG_SPEED_RANGE_4 (2 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +#define PWM_MAX31790_FLAG_SPEED_RANGE_8 (3 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +#define PWM_MAX31790_FLAG_SPEED_RANGE_16 (4 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +#define PWM_MAX31790_FLAG_SPEED_RANGE_32 (5 << PWM_MAX31790_FLAG_SPEED_RANGE_POS) +/*! + * @brief PWM rate of change + * + * Configures the internal control loop of the fan. Details about these values can be found + * in the datasheet of the MAX31790. + */ +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_0 (0 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_1 (1 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_2 (2 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_3 (3 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_4 (4 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_5 (5 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_6 (6 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +#define PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_7 (7 << PWM_MAX31790_FLAG_PWM_RATE_OF_CHANGE_POS) +/*! + * @brief activate spin up for fan + * + * This activates the spin up of the fan, which means that the controller will force the fan + * to maximum speed for a startup from a completely stopped state. + */ +#define PWM_MAX31790_FLAG_SPIN_UP (1 << PWM_MAX31790_FLAG_SPIN_UP_POS) +/** @} */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_PWM_MAX31790_H_ */