commit 601f90ef6e6c117f51e4c771fd23627467c66553 Author: Eduard Iten Date: Thu Jul 17 10:03:20 2025 +0200 Initial commit of the vnd_module diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bdb5141 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(drivers) \ No newline at end of file diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..78189a5 --- /dev/null +++ b/Kconfig @@ -0,0 +1 @@ +source "drivers/Kconfig" \ No newline at end of file diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt new file mode 100644 index 0000000..39da9f8 --- /dev/null +++ b/drivers/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(misc) \ No newline at end of file diff --git a/drivers/Kconfig b/drivers/Kconfig new file mode 100644 index 0000000..33fee47 --- /dev/null +++ b/drivers/Kconfig @@ -0,0 +1 @@ +source "misc/Kconfig" \ No newline at end of file diff --git a/drivers/misc/CMakeLists.txt b/drivers/misc/CMakeLists.txt new file mode 100644 index 0000000..124b68f --- /dev/null +++ b/drivers/misc/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory_ifdef(CONFIG_VND7050AJ vnd7050aj) \ No newline at end of file diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig new file mode 100644 index 0000000..59ba9ea --- /dev/null +++ b/drivers/misc/Kconfig @@ -0,0 +1 @@ +source "vnd7050aj/Kconfig" \ No newline at end of file diff --git a/drivers/misc/vnd7050aj/CMakeLists.txt b/drivers/misc/vnd7050aj/CMakeLists.txt new file mode 100644 index 0000000..d5ad828 --- /dev/null +++ b/drivers/misc/vnd7050aj/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(vnd7050aj.c) \ No newline at end of file diff --git a/drivers/misc/vnd7050aj/Kconfig b/drivers/misc/vnd7050aj/Kconfig new file mode 100644 index 0000000..50739de --- /dev/null +++ b/drivers/misc/vnd7050aj/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: Apache-2.0 + +config VND7050AJ + bool "VND7050AJ driver" + default n + select ADC + select GPIO + help + Enable support for the VND7050AJ high-side driver. + +if VND7050AJ + config VND7050AJ_INIT_PRIORITY + int "VND7050AJ initialization priority" + default 80 + help + VND7050AJ driver initialization priority. This should be set to a value + that ensures the driver is initialized after the required subsystems + (like GPIO, ADC) but before application code runs. + + config VND7050AJ_LOG_LEVEL + int "VND7050AJ Log level" + depends on LOG + default 3 + range 0 4 + help + Sets log level for VND7050AJ driver. + Levels are: + 0 OFF, do not write + 1 ERROR, only write LOG_ERR + 2 WARNING, write LOG_WRN in addition to previous level + 3 INFO, write LOG_INF in addition to previous levels + 4 DEBUG, write LOG_DBG in addition to previous levels +endif # VND7050AJ diff --git a/drivers/misc/vnd7050aj/vnd7050aj.c b/drivers/misc/vnd7050aj/vnd7050aj.c new file mode 100644 index 0000000..7218161 --- /dev/null +++ b/drivers/misc/vnd7050aj/vnd7050aj.c @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2025, Eduard Iten + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT st_vnd7050aj + +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(VND7050AJ, CONFIG_VND7050AJ_LOG_LEVEL); + +/* Diagnostic selection modes */ +enum vnd7050aj_diag_mode { + DIAG_CURRENT_CH0, + DIAG_CURRENT_CH1, + DIAG_VCC, + DIAG_TEMP, +}; + +struct vnd7050aj_config { + struct gpio_dt_spec input0_gpio; + struct gpio_dt_spec input1_gpio; + struct gpio_dt_spec sel0_gpio; + struct gpio_dt_spec sel1_gpio; + struct gpio_dt_spec sen_gpio; + struct gpio_dt_spec fault_reset_gpio; + struct adc_dt_spec io_channels; + uint32_t r_sense_ohms; + uint32_t k_factor; /* Current sense ratio */ + uint32_t k_vcc; /* VCC sense ratio * 1000 */ + int32_t t_sense_0; /* Temp sense reference temperature in °C */ + uint32_t v_sense_0; /* Temp sense reference voltage in mV */ + uint32_t k_tchip; /* Temp sense gain in °C/mV * 1000 */ +}; + +struct vnd7050aj_data { + struct k_mutex lock; +}; + +static int vnd7050aj_init(const struct device *dev) +{ + const struct vnd7050aj_config *config = dev->config; + struct vnd7050aj_data *data = dev->data; + int err; + + k_mutex_init(&data->lock); + + LOG_DBG("Initializing VND7050AJ device %s", dev->name); + + /* --- Check if all required devices are ready --- */ + if (!gpio_is_ready_dt(&config->input0_gpio)) { + LOG_ERR("Input0 GPIO port is not ready"); + return -ENODEV; + } + if (!gpio_is_ready_dt(&config->input1_gpio)) { + LOG_ERR("Input1 GPIO port is not ready"); + return -ENODEV; + } + if (!gpio_is_ready_dt(&config->sel0_gpio)) { + LOG_ERR("Select0 GPIO port is not ready"); + return -ENODEV; + } + if (!gpio_is_ready_dt(&config->sel1_gpio)) { + LOG_ERR("Select1 GPIO port is not ready"); + return -ENODEV; + } + if (!gpio_is_ready_dt(&config->sen_gpio)) { + LOG_ERR("Sense GPIO port is not ready"); + return -ENODEV; + } + if (!gpio_is_ready_dt(&config->fault_reset_gpio)) { + LOG_ERR("Fault Reset GPIO port is not ready"); + return -ENODEV; + } + + if (!adc_is_ready_dt(&config->io_channels)) { + LOG_ERR("ADC controller not ready"); + return -ENODEV; + } + + /* --- Configure GPIOs to their initial states --- */ + err = gpio_pin_configure_dt(&config->input0_gpio, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Failed to configure input0 GPIO: %d", err); + return err; + } + + err = gpio_pin_configure_dt(&config->input1_gpio, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Failed to configure input1 GPIO: %d", err); + return err; + } + err = gpio_pin_configure_dt(&config->sel0_gpio, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Failed to configure select0 GPIO: %d", err); + return err; + } + err = gpio_pin_configure_dt(&config->sel1_gpio, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Failed to configure select1 GPIO: %d", err); + return err; + } + err = gpio_pin_configure_dt(&config->sen_gpio, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Failed to configure sense GPIO: %d", err); + return err; + } + err = gpio_pin_configure_dt(&config->fault_reset_gpio, + GPIO_OUTPUT_ACTIVE); /* Active-low, so init high */ + if (err) { + LOG_ERR("Failed to configure fault reset GPIO: %d", err); + return err; + } + + /* --- Configure the ADC channel --- */ + err = adc_channel_setup_dt(&config->io_channels); + if (err) { + LOG_ERR("Failed to setup ADC channel: %d", err); + return err; + } + + LOG_DBG("Device %s initialized", dev->name); + return 0; +} + +#define VND7050AJ_DEFINE(inst) \ + static struct vnd7050aj_data vnd7050aj_data_##inst; \ + \ + static const struct vnd7050aj_config vnd7050aj_config_##inst = { \ + .input0_gpio = GPIO_DT_SPEC_INST_GET(inst, input0_gpios), \ + .input1_gpio = GPIO_DT_SPEC_INST_GET(inst, input1_gpios), \ + .sel0_gpio = GPIO_DT_SPEC_INST_GET(inst, select0_gpios), \ + .sel1_gpio = GPIO_DT_SPEC_INST_GET(inst, select1_gpios), \ + .sen_gpio = GPIO_DT_SPEC_INST_GET(inst, sense_enable_gpios), \ + .fault_reset_gpio = GPIO_DT_SPEC_INST_GET(inst, fault_reset_gpios), \ + .io_channels = ADC_DT_SPEC_INST_GET(inst), \ + .r_sense_ohms = DT_INST_PROP(inst, r_sense_ohms), \ + .k_factor = DT_INST_PROP(inst, k_factor), \ + .k_vcc = DT_INST_PROP(inst, k_vcc), \ + .t_sense_0 = DT_INST_PROP(inst, t_sense_0), \ + .v_sense_0 = DT_INST_PROP(inst, v_sense_0), \ + .k_tchip = DT_INST_PROP(inst, k_tchip), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, vnd7050aj_init, NULL, /* No PM support yet */ \ + &vnd7050aj_data_##inst, &vnd7050aj_config_##inst, POST_KERNEL, \ + CONFIG_VND7050AJ_INIT_PRIORITY, \ + NULL); /* No API struct needed for custom API */ + +DT_INST_FOREACH_STATUS_OKAY(VND7050AJ_DEFINE) + +int vnd7050aj_set_output_state(const struct device *dev, uint8_t channel, bool state) +{ + const struct vnd7050aj_config *config = dev->config; + + if (channel != VND7050AJ_CHANNEL_0 && channel != VND7050AJ_CHANNEL_1) { + return -EINVAL; + } + + const struct gpio_dt_spec *gpio = + (channel == VND7050AJ_CHANNEL_0) ? &config->input0_gpio : &config->input1_gpio; + + return gpio_pin_set_dt(gpio, (int)state); +} + +static int vnd7050aj_read_sense_voltage(const struct device *dev, enum vnd7050aj_diag_mode mode, + int32_t *voltage_mv) +{ + const struct vnd7050aj_config *config = dev->config; + struct vnd7050aj_data *data = dev->data; + int err = 0; + + /* Initialize the buffer to zero */ + *voltage_mv = 0; + + struct adc_sequence sequence = { + .buffer = voltage_mv, + .buffer_size = sizeof(*voltage_mv), +#ifdef CONFIG_ADC_CALIBRATION + .calibrate = true, +#endif + }; + adc_sequence_init_dt(&config->io_channels, &sequence); + + k_mutex_lock(&data->lock, K_FOREVER); + + /* Step 1: Select diagnostic mode */ + switch (mode) { + case DIAG_CURRENT_CH0: + gpio_pin_set_dt(&config->sel0_gpio, 0); + gpio_pin_set_dt(&config->sel1_gpio, 0); + gpio_pin_set_dt(&config->sen_gpio, 1); + break; + case DIAG_CURRENT_CH1: + gpio_pin_set_dt(&config->sel0_gpio, 0); + gpio_pin_set_dt(&config->sel1_gpio, 1); + gpio_pin_set_dt(&config->sen_gpio, 1); + break; + case DIAG_TEMP: + gpio_pin_set_dt(&config->sel0_gpio, 1); + gpio_pin_set_dt(&config->sel1_gpio, 0); + gpio_pin_set_dt(&config->sen_gpio, 1); + break; + case DIAG_VCC: + gpio_pin_set_dt(&config->sel1_gpio, 1); + gpio_pin_set_dt(&config->sel0_gpio, 1); + gpio_pin_set_dt(&config->sen_gpio, 1); + break; + default: + err = -ENOTSUP; + goto cleanup; + } + + /* Allow time for GPIO changes to settle and ADC input to stabilize */ + k_msleep(1); + + /* Initialize buffer before read */ + *voltage_mv = 0; + err = adc_read_dt(&config->io_channels, &sequence); + if (err) { + LOG_ERR("ADC read failed: %d", err); + goto cleanup; + } + + LOG_DBG("ADC read completed, raw value: %d", *voltage_mv); + err = adc_raw_to_millivolts_dt(&config->io_channels, voltage_mv); + if (err) { + LOG_ERR("ADC raw to millivolts conversion failed: %d", err); + goto cleanup; + } + LOG_DBG("ADC Reading (without processing) %dmV", *voltage_mv); + +cleanup: + /* Deactivate sense enable to save power */ + gpio_pin_set_dt(&config->sen_gpio, 0); + k_mutex_unlock(&data->lock); + return err; +} + +int vnd7050aj_read_load_current(const struct device *dev, uint8_t channel, int32_t *current_ma) +{ + const struct vnd7050aj_config *config = dev->config; + int32_t sense_mv; + int err; + + if (channel != VND7050AJ_CHANNEL_0 && channel != VND7050AJ_CHANNEL_1) { + return -EINVAL; + } + + enum vnd7050aj_diag_mode mode = + (channel == VND7050AJ_CHANNEL_0) ? DIAG_CURRENT_CH0 : DIAG_CURRENT_CH1; + + err = vnd7050aj_read_sense_voltage(dev, mode, &sense_mv); + if (err) { + return err; + } + + /* Formula according to datasheet: I_OUT = (V_SENSE / R_SENSE) * K_IL */ + /* To avoid floating point, we calculate in microamps and then convert to milliamps */ + int64_t current_ua = ((int64_t)sense_mv * 1000 * config->k_factor) / config->r_sense_ohms; + *current_ma = (int32_t)(current_ua / 1000); + + return 0; +} + +int vnd7050aj_read_chip_temp(const struct device *dev, int32_t *temp_c) +{ + const struct vnd7050aj_config *config = dev->config; + int32_t sense_mv; + int err; + + err = vnd7050aj_read_sense_voltage(dev, DIAG_TEMP, &sense_mv); + if (err) { + return err; + } + + /* Calculate temperature difference in kelvin first to avoid overflow */ + int32_t voltage_diff = sense_mv - (int32_t)config->v_sense_0; + int32_t temp_diff_kelvin = (voltage_diff * 1000) / (int32_t)config->k_tchip; + + *temp_c = config->t_sense_0 + temp_diff_kelvin; + + LOG_DBG("Voltage diff: %d mV, Temp diff: %d milli°C, Final temp: %d °C", voltage_diff, + temp_diff_kelvin, *temp_c); + + return 0; +} + +int vnd7050aj_read_supply_voltage(const struct device *dev, int32_t *voltage_mv) +{ + const struct vnd7050aj_config *config = dev->config; + int32_t sense_mv; + int err; + + err = vnd7050aj_read_sense_voltage(dev, DIAG_VCC, &sense_mv); + if (err) { + return err; + } + + /* Formula from datasheet: VCC = V_SENSE * K_VCC */ + *voltage_mv = (sense_mv * config->k_vcc) / 1000; + + return 0; +} + +int vnd7050aj_reset_fault(const struct device *dev) +{ + const struct vnd7050aj_config *config = dev->config; + int err; + + /* Pulse the active-low fault reset pin */ + err = gpio_pin_set_dt(&config->fault_reset_gpio, 0); + if (err) { + return err; + } + k_msleep(1); /* Short pulse */ + err = gpio_pin_set_dt(&config->fault_reset_gpio, 1); + + return err; +} diff --git a/dts/bindings/misc/st,vnd7050aj.yaml b/dts/bindings/misc/st,vnd7050aj.yaml new file mode 100644 index 0000000..265a98f --- /dev/null +++ b/dts/bindings/misc/st,vnd7050aj.yaml @@ -0,0 +1,87 @@ +# Copyright (c) 2024, Eduard Iten +# SPDX-License-Identifier: Apache-2.0 + +description: | + STMicroelectronics VND7050AJ dual-channel high-side driver. + This is a GPIO and ADC controlled device. + +compatible: "st,vnd7050aj" + +include: base.yaml + +properties: + input0-gpios: + type: phandle-array + required: true + description: GPIO to control output channel 0. + + input1-gpios: + type: phandle-array + required: true + description: GPIO to control output channel 1. + + select0-gpios: + type: phandle-array + required: true + description: GPIO for MultiSense selection bit 0. + + select1-gpios: + type: phandle-array + required: true + description: GPIO for MultiSense selection bit 1. + + sense-enable-gpios: + type: phandle-array + required: true + description: GPIO to enable the MultiSense output. + + fault-reset-gpios: + type: phandle-array + required: true + description: GPIO to reset a latched fault (active-low). + + io-channels: + type: phandle-array + required: true + description: | + ADC channel connected to the MultiSense pin. This should be an + io-channels property pointing to the ADC controller and channel number. + + r-sense-ohms: + type: int + required: true + description: | + Value of the external sense resistor connected from the MultiSense + pin to GND, specified in Ohms. This is critical for correct + conversion of the analog readings. + + k-factor: + type: int + default: 1500 + description: | + Factor between PowerMOS and SenseMOS. + + k-vcc: + type: int + default: 8000 + description: | + VCC sense ratio multiplied by 1000. Used for supply voltage calculation. + + t-sense-0: + type: int + default: 25 + description: | + Temperature sense reference temperature in degrees Celsius. + + v-sense-0: + type: int + default: 2070 + description: | + Temperature sense reference voltage in millivolts. + + k-tchip: + type: int + default: -5500 + description: | + Temperature sense gain coefficient multiplied by 1000. + Used for chip temperature calculation. diff --git a/include/zephyr/drivers/misc/vnd7050aj/vnd7050aj.h b/include/zephyr/drivers/misc/vnd7050aj/vnd7050aj.h new file mode 100644 index 0000000..f253d93 --- /dev/null +++ b/include/zephyr/drivers/misc/vnd7050aj/vnd7050aj.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025, Eduard Iten + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Channel identifiers for the VND7050AJ. + */ +#define VND7050AJ_CHANNEL_0 0 +#define VND7050AJ_CHANNEL_1 1 + +/** + * @brief Sets the state of a specific output channel. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The channel to control (VND7050AJ_CHANNEL_0 or VND7050AJ_CHANNEL_1). + * @param state The desired state (true for ON, false for OFF). + * @return 0 on success, negative error code on failure. + */ +int vnd7050aj_set_output_state(const struct device *dev, uint8_t channel, bool state); + +/** + * @brief Reads the load current for a specific channel. + * + * @param dev Pointer to the device structure for the driver instance. + * @param channel The channel to measure (VND7050AJ_CHANNEL_0 or VND7050AJ_CHANNEL_1). + * @param[out] current_ma Pointer to store the measured current in milliamperes (mA). + * @return 0 on success, negative error code on failure. + */ +int vnd7050aj_read_load_current(const struct device *dev, uint8_t channel, int32_t *current_ma); + +/** + * @brief Reads the VCC supply voltage. + * + * @param dev Pointer to the device structure for the driver instance. + * @param[out] voltage_mv Pointer to store the measured voltage in millivolts (mV). + * @return 0 on success, negative error code on failure. + */ +int vnd7050aj_read_supply_voltage(const struct device *dev, int32_t *voltage_mv); + +/** + * @brief Reads the internal chip temperature. + * + * @param dev Pointer to the device structure for the driver instance. + * @param[out] temp_c Pointer to store the measured temperature in degrees Celsius (°C). + * @return 0 on success, negative error code on failure. + */ +int vnd7050aj_read_chip_temp(const struct device *dev, int32_t *temp_c); + +/** + * @brief Resets a latched fault condition. + * + * This function sends a low pulse to the FaultRST pin. + * + * @param dev Pointer to the device structure for the driver instance. + * @return 0 on success, negative error code on failure. + */ +int vnd7050aj_reset_fault(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MISC_VND7050AJ_H_ */