-
Notifications
You must be signed in to change notification settings - Fork 2.1k
drivers/gp8xxx: add initial support #21182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| include $(RIOTMAKE)/driver_with_saul.mk |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| FEATURES_REQUIRED += periph_i2c |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| # include variants of GP8xxx drivers as pseudo modules | ||
| PSEUDOMODULES += gp8503 | ||
| PSEUDOMODULES += gp8512 | ||
| PSEUDOMODULES += gp8211s | ||
| PSEUDOMODULES += gp8403 | ||
| PSEUDOMODULES += gp8413 | ||
| PSEUDOMODULES += gp8302 | ||
|
|
||
| USEMODULE_INCLUDES_gp8xxx := $(LAST_MAKEFILEDIR)/include | ||
| USEMODULE_INCLUDES += $(USEMODULE_INCLUDES_gp8xxx) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com> | ||
| * SPDX-License-Identifier: LGPL-2.1-only | ||
| */ | ||
|
|
||
| /** | ||
| * @ingroup drivers_gp8xxx | ||
| * @{ | ||
| * | ||
| * @file | ||
| * @brief Device driver implementation for the gp8xxx | ||
| * | ||
| * @author Bas Stottelaar <basstottelaar@gmail.com> | ||
| * | ||
| * @} | ||
| */ | ||
|
|
||
| #include <assert.h> | ||
| #include <errno.h> | ||
| #include <string.h> | ||
|
|
||
| #include "gp8xxx.h" | ||
| #include "gp8xxx_constants.h" | ||
| #include "gp8xxx_params.h" | ||
|
|
||
| #define ENABLE_DEBUG 0 | ||
| #include "debug.h" | ||
|
|
||
| #define GP8XXX_I2C (dev->params.i2c_dev) | ||
| #define GP8XXX_ADDR (dev->params.address) | ||
|
|
||
| #if GP8XXX_HAS_VDAC | ||
| # define GP8XXX_RANGE (dev->params.range) | ||
| #endif | ||
|
|
||
| int gp8xxx_init(gp8xxx_t *dev, const gp8xxx_params_t *params) | ||
| { | ||
| /* initialize the device descriptor */ | ||
| dev->params = *params; | ||
|
|
||
| /* setup the i2c bus */ | ||
| i2c_acquire(GP8XXX_I2C); | ||
|
|
||
| /* initialize the peripheral */ | ||
| if (i2c_write_byte(GP8XXX_I2C, GP8XXX_ADDR, 0, 0) != 0) { | ||
| i2c_release(GP8XXX_I2C); | ||
| DEBUG("[gp8xxx] gp8xxx_init: init failed\n"); | ||
| return -ENODEV; | ||
| } | ||
|
|
||
| i2c_release(GP8XXX_I2C); | ||
|
|
||
| /* configure a peripheral to a known state */ | ||
| #if GP8XXX_HAS_VDAC | ||
| if (dev->params.info->type == GP8XXX_INFO_TYPE_VDAC) { | ||
| gp8xxx_set_voltage_range(dev, GP8XXX_RANGE); | ||
| gp8xxx_set_voltage(dev, GP8XXX_CHANNEL_ALL, 0); | ||
| } | ||
| #endif | ||
| #if GP8XXX_HAS_IDAC | ||
| if (dev->params.info->type == GP8XXX_INFO_TYPE_IDAC) { | ||
| dev->range = GP8XXX_OUTPUT_RANGE_25_MA; | ||
| gp8xxx_set_current(dev, GP8XXX_CHANNEL_ALL, 0); | ||
| } | ||
| #endif | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int gp8xxx_set_dac(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t value) | ||
| { | ||
| assert(channel < dev->params.info->channels || channel == GP8XXX_CHANNEL_ALL); | ||
| assert(value <= dev->params.info->resolution); | ||
|
|
||
| DEBUG("[gp8xxx] gp8xxx_set_dac: channel=%d value=%d\n", (int)channel, value); | ||
|
|
||
| /* value must be sent MSB */ | ||
| if (dev->params.info->resolution == GP8XXX_INFO_RESOLUTION_12_BIT) { | ||
| value = value << 4; | ||
| } | ||
| else if (dev->params.info->resolution == GP8XXX_INFO_RESOLUTION_15_BIT) { | ||
| value = value << 1; | ||
| } | ||
|
|
||
| /* when both channels are selected, write 4 bytes instead of 2 bytes */ | ||
| uint8_t out[] = { | ||
| GP8XXX_REG_CHANNEL << (int)(channel == GP8XXX_CHANNEL_ALL ? 0 : channel), | ||
| value & 0xff, | ||
| value >> 8, | ||
| value & 0xff, | ||
| value >> 8 | ||
| }; | ||
|
|
||
| size_t size = (channel == GP8XXX_CHANNEL_ALL ? 5 : 3); | ||
|
|
||
| i2c_acquire(GP8XXX_I2C); | ||
|
|
||
| if (i2c_write_bytes(GP8XXX_I2C, GP8XXX_ADDR, &out, size, 0) != 0) { | ||
| i2c_release(GP8XXX_I2C); | ||
| DEBUG("[gp8xxx] gp8xxx_set_dac: set raw value failed\n"); | ||
| return -EIO; | ||
| } | ||
|
|
||
| i2c_release(GP8XXX_I2C); | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| #if GP8XXX_HAS_VDAC | ||
| int gp8xxx_set_voltage_range(gp8xxx_t *dev, gp8xxx_output_range_t range) | ||
| { | ||
| assert(dev->params.info->type == GP8XXX_INFO_TYPE_VDAC); | ||
|
|
||
| /* only the 5 V/10 V models have a customizable range */ | ||
| if (dev->params.info->range == GP8XXX_INFO_RANGE_5V_10V) { | ||
| uint8_t value = 0x00; | ||
|
|
||
| switch (range) { | ||
| case GP8XXX_OUTPUT_RANGE_5V: | ||
| value = 0x00; | ||
| break; | ||
| case GP8XXX_OUTPUT_RANGE_10V: | ||
| value = 0x11; | ||
| break; | ||
| default: | ||
| return -ERANGE; | ||
| } | ||
|
|
||
| i2c_acquire(GP8XXX_I2C); | ||
|
|
||
| if (i2c_write_reg(GP8XXX_I2C, GP8XXX_ADDR, GP8XXX_REG_OUTPUT_RANGE, value, 0) != 0) { | ||
| DEBUG("[gp8xxx] gp8xxx_set_voltage_range: cannot set voltage range\n"); | ||
| i2c_release(GP8XXX_I2C); | ||
basilfx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return -EIO; | ||
| } | ||
|
|
||
| i2c_release(GP8XXX_I2C); | ||
| } | ||
|
|
||
| /* store range for calculation purposes */ | ||
| dev->range = range; | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| int gp8xxx_set_voltage(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t voltage) | ||
| { | ||
| assert(dev->params.info->type == GP8XXX_INFO_TYPE_VDAC); | ||
|
|
||
| if (voltage > dev->range) { | ||
| DEBUG("[gp8xxx] gp8xxx_set_voltage: voltage out of range\n"); | ||
| return -ERANGE; | ||
| } | ||
|
|
||
| uint32_t value = ((uint32_t)voltage * (dev->params.info->resolution - 1) + (dev->range / 2)) / | ||
| dev->range; | ||
|
|
||
| return gp8xxx_set_dac(dev, channel, (uint16_t)value); | ||
| } | ||
| #endif /* GP8XXX_HAS_VDAC */ | ||
|
|
||
| #if GP8XXX_HAS_IDAC | ||
| void gp8xxx_set_current_calibration(gp8xxx_t *dev, uint16_t calibration_4ma, | ||
| uint16_t calibration_20ma) | ||
| { | ||
| assert(dev->params.info->type == GP8XXX_INFO_TYPE_IDAC); | ||
| assert((calibration_4ma == 0 && calibration_20ma == 0) || | ||
| (calibration_4ma < calibration_20ma && calibration_4ma < dev->params.info->resolution && | ||
| calibration_20ma <= dev->params.info->resolution)); | ||
|
|
||
| dev->calibration_4ma = calibration_4ma; | ||
| dev->calibration_20ma = calibration_20ma; | ||
| } | ||
|
|
||
| int gp8xxx_set_current(gp8xxx_t *dev, gp8xxx_channel_t channel, uint16_t current) | ||
| { | ||
| assert(dev->params.info->type == GP8XXX_INFO_TYPE_IDAC); | ||
|
|
||
| /* ensure current is within configured range */ | ||
| if (current > dev->range) { | ||
| DEBUG("[gp8xxx] gp8xxx_set_current: current out of range\n"); | ||
| return -ERANGE; | ||
| } | ||
|
|
||
| /* compute ADC value based on either calibration data or configured range */ | ||
| uint32_t value; | ||
|
|
||
| if (dev->calibration_4ma > 0 && dev->calibration_20ma > 0 && current >= 4000 && | ||
| current <= 20000) { | ||
| value = dev->calibration_4ma + | ||
| ((uint32_t)(current - 4000) * (dev->calibration_20ma - dev->calibration_4ma)) / | ||
| (20000 - 4000); | ||
| } | ||
| else { | ||
| value = ((uint32_t)current * (dev->params.info->resolution - 1) + (dev->range / 2)) / | ||
crasbe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| (dev->range); | ||
| } | ||
|
|
||
| return gp8xxx_set_dac(dev, channel, (uint16_t)value); | ||
| } | ||
| #endif /* GP8XXX_HAS_IDAC */ | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com> | ||
| * SPDX-License-Identifier: LGPL-2.1-only | ||
| */ | ||
|
|
||
| /** | ||
| * @ingroup drivers_gp8xxx | ||
| * @{ | ||
| * | ||
| * @file | ||
| * @brief Definitions of the Guestgood GP8xxx I2C DACs | ||
| * | ||
| * @author Bas Stottelaar <basstottelaar@gmail.com> | ||
| * | ||
| * @} | ||
| */ | ||
|
|
||
| #include "gp8xxx_info.h" | ||
|
|
||
| const gp8xxx_info_t gp8503_info = { | ||
| .type = GP8503_INFO_TYPE, | ||
| .channels = GP8503_INFO_CHANNELS, | ||
| .range = GP8503_INFO_RANGE, | ||
| .resolution = GP8503_INFO_RESOLUTION, | ||
| }; | ||
|
|
||
| const gp8xxx_info_t gp8512_info = { | ||
| .type = GP8512_INFO_TYPE, | ||
| .channels = GP8512_INFO_CHANNELS, | ||
| .range = GP8512_INFO_RANGE, | ||
| .resolution = GP8512_INFO_RESOLUTION, | ||
| }; | ||
|
|
||
| const gp8xxx_info_t gp8211s_info = { | ||
| .type = GP8211S_INFO_TYPE, | ||
| .channels = GP8211S_INFO_CHANNELS, | ||
| .range = GP8211S_INFO_RANGE, | ||
| .resolution = GP8211S_INFO_RESOLUTION, | ||
| }; | ||
|
|
||
| const gp8xxx_info_t gp8403_info = { | ||
| .type = GP8403_INFO_TYPE, | ||
| .channels = GP8403_INFO_CHANNELS, | ||
| .range = GP8403_INFO_RANGE, | ||
| .resolution = GP8403_INFO_RESOLUTION, | ||
| }; | ||
|
|
||
| const gp8xxx_info_t gp8413_info = { | ||
| .type = GP8413_INFO_TYPE, | ||
| .channels = GP8413_INFO_CHANNELS, | ||
| .range = GP8413_INFO_RANGE, | ||
| .resolution = GP8413_INFO_RESOLUTION, | ||
| }; | ||
|
|
||
| const gp8xxx_info_t gp8302_info = { | ||
| .type = GP8302_INFO_TYPE, | ||
| .channels = GP8302_INFO_CHANNELS, | ||
| .range = GP8302_INFO_RANGE, | ||
| .resolution = GP8302_INFO_RESOLUTION, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: 2025 Bas Stottelaar <basstottelaar@gmail.com> | ||
| * SPDX-License-Identifier: LGPL-2.1-only | ||
| */ | ||
|
|
||
| /** | ||
| * @ingroup drivers_gp8xxx | ||
| * @{ | ||
| * | ||
| * @file | ||
| * @brief SAUL adoption for the Guestgood GP8xxx I2C DACs | ||
| * | ||
| * @author Bas Stottelaar <basstottelaar@gmail.com> | ||
| * | ||
| * @} | ||
| */ | ||
|
|
||
| #include <math.h> | ||
|
|
||
| #include "saul.h" | ||
|
|
||
| #include "gp8xxx.h" | ||
|
|
||
| #define ENABLE_DEBUG 0 | ||
| #include "debug.h" | ||
|
|
||
| static uint16_t convert(uint16_t value, int scale) | ||
| { | ||
| if (scale > 0) { | ||
| return value / pow(10, scale); | ||
| } | ||
| else if (scale < 0) { | ||
| return value * pow(10, -scale); | ||
| } | ||
|
|
||
| return value; | ||
| } | ||
|
|
||
| #if GP8XXX_HAS_VDAC | ||
| static int set_voltage(const void *dev, const phydat_t *data) | ||
| { | ||
| gp8xxx_saul_t *saul_dev = (gp8xxx_saul_t *)dev; | ||
|
|
||
| DEBUG("[gp8xxx_saul] set_voltage value=%d scale=%d\n", data->val[0], data->scale); | ||
|
|
||
| /* convert value to mV */ | ||
| uint16_t value = convert(data->val[0], -3 - data->scale); | ||
|
|
||
| /* write voltage*/ | ||
| int res = gp8xxx_set_voltage(saul_dev->dev, saul_dev->channel, value); | ||
|
|
||
| if (res != 0) { | ||
| return -ECANCELED; | ||
| } | ||
|
|
||
| return 1; | ||
| } | ||
| #endif | ||
|
|
||
| #if GP8XXX_HAS_IDAC | ||
| static int set_current(const void *dev, const phydat_t *data) | ||
| { | ||
| gp8xxx_saul_t *saul_dev = (gp8xxx_saul_t *)dev; | ||
|
|
||
| DEBUG("[gp8xxx_saul] set_current value=%d scale=%d\n", data->val[0], data->scale); | ||
|
|
||
| /* convert value to uA */ | ||
| uint16_t value = convert(data->val[0], -6 - data->scale); | ||
|
|
||
| /* write current */ | ||
| int res = gp8xxx_set_current(saul_dev->dev, saul_dev->channel, value); | ||
|
|
||
| if (res != 0) { | ||
| return -ECANCELED; | ||
| } | ||
|
|
||
| return 1; | ||
| } | ||
| #endif | ||
|
|
||
| #if GP8XXX_HAS_VDAC | ||
| const saul_driver_t gp8xxx_voltage_saul_driver = { | ||
| .read = saul_read_notsup, | ||
| .write = set_voltage, | ||
| .type = SAUL_ACT_VOLTAGE, | ||
| }; | ||
| #endif | ||
|
|
||
| #if GP8XXX_HAS_IDAC | ||
| const saul_driver_t gp8xxx_current_saul_driver = { | ||
| .read = saul_read_notsup, | ||
| .write = set_current, | ||
| .type = SAUL_ACT_CURRENT, | ||
| }; | ||
| #endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it make sense to return an error when the range wasn't actually set because the device doesn't support it? -ENOSUP for example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided that wrong use would fall in the use of programming errors, and that an assert would be sufficient alternative to another return value or making it type safer.