Skip to content

Add support for MAX17616#2734

Open
cjones-adi wants to merge 6 commits intoanalogdevicesinc:mainfrom
cjones-adi:dev/max17616
Open

Add support for MAX17616#2734
cjones-adi wants to merge 6 commits intoanalogdevicesinc:mainfrom
cjones-adi:dev/max17616

Conversation

@cjones-adi
Copy link
Contributor

Pull Request Description

The MAX17616/MAX17616A offers highly versatile and programmable protection boundaries for systems against input voltage faults and output overcurrent faults. Input-voltage faults (with positive polarity) are protected up to +80V (without Reverse Current Protection)/+75V (with Reverse Current Protection), by an internal nFET featuring low ON-resistance (20mΩ typ). The devices feature a programmable undervoltage-lockout (UVLO) thresholds by using external voltage- dividers. The MAX17616 features a programmable overvoltage-lockout (OVLO) while MAX17616A offers a programmable output voltage clamp function through the OVFB pin that features an output voltage limiting regulation during input transient surge events. Input undervoltage and overvoltage protection (MAX17616)/output voltage clamp function (MAX17616A) can be programmed across the entire 3V to 80V operating range.

PR Type

  • Bug fix (change that fixes an issue)
  • New feature (change that adds new functionality)
  • Breaking change (has dependencies in other repos or will cause CI to fail)

PR Checklist

  • I have followed the Coding style guidelines
  • I have complied with the Submission Checklist
  • I have performed a self-review of the changes
  • I have commented my code, at least hard-to-understand parts
  • I have build all projects affected by the changes in this PR
  • I have tested in hardware affected projects, at the relevant boards
  • I have signed off all commits from this PR
  • I have updated the documentation (wiki pages, ReadMe etc), if applies

@CLAassistant
Copy link

CLAassistant commented Sep 25, 2025

CLA assistant check
All committers have signed the CLA.

@buha
Copy link
Contributor

buha commented Sep 29, 2025

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@kister-jimenez
Copy link
Collaborator

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@cjones-adi
Copy link
Contributor Author

cjones-adi commented Sep 30, 2025

V2:

Rebase to latest main
Move max17616.rst to respective doc/sphinx/source directory
Fix astyle errors on identified files

@kister-jimenez
Copy link
Collaborator

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@cjones-adi
Copy link
Contributor Author

V3:

Resolve identified Sphinx documentation errors

@jemfgeronimo
Copy link
Contributor

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@cjones-adi
Copy link
Contributor Author

V4:

Remove diff headers included in max17616.h

@cjones-adi cjones-adi force-pushed the dev/max17616 branch 2 times, most recently from b1687b3 to 39f0f5a Compare October 1, 2025 04:43
@cjones-adi
Copy link
Contributor Author

/AzurePipelines run

@azure-pipelines
Copy link

Commenter does not have sufficient privileges for PR 2734 in repo analogdevicesinc/no-OS

@kister-jimenez
Copy link
Collaborator

/AzurePipelines run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@jemfgeronimo jemfgeronimo self-requested a review October 27, 2025 08:22
Copy link
Member

@kseerp kseerp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, minor comments on my end.

Comment on lines 491 to 493
if (ret == 0)
*value = max17616_direct_to_int(raw_value,
&max17616_vin_coeffs);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error handling be done first, and only do the value conversion if the read was successful

ret = max17616_read_word(dev, MAX17616_CMD(MAX17616_READ_VIN),
					 &raw_value);				 
if (ret)
        return ret;
        
*value = max17616_direct_to_int(raw_value,
							&max17616_vin_coeffs);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

case MAX17616_POWER:
/* Calculate power from voltage and current */
{
int vin, iout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

variable declarations should be moved to top of function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

Comment on lines 528 to 529
if (ret == 0)
*value = vin * iout;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error handling be done first before processing success case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

Comment on lines 566 to 570
int ret = max17616_read_byte(dev,MAX17616_CMD(MAX17616_OPERATION),
&operation);

if (ret == 0)
*enabled = (operation & 0x80) ? true : false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.


/* Extract bits 7:6 and convert to enum */
switch (raw_value & 0xC0) {
case 0x00:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using macros in switch cases

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.


/* Extract bits 3:0 and convert to enum */
switch (raw_value & 0x0F) {
case 0x00:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

Comment on lines 213 to 220
MAX17616_STATUS_BIT_GENERAL = 0, // Not supported
MAX17616_STATUS_BIT_CML = 1, // Comms, memory, or logic Fault
MAX17616_STATUS_BIT_TEMPERATURE = 2, // Temperature Fault or Warning
MAX17616_STATUS_BIT_VIN_UV = 3, // Input Under Voltage Fault
MAX17616_STATUS_BIT_IOUT_OC = 4, // Output Over Current Fault
MAX17616_STATUS_BIT_VOUT_OV = 5, // An output overvoltage fault occurred
MAX17616_STATUS_BIT_OFF = 6, // Max17616 is not enabled
MAX17616_STATUS_BIT_BUSY = 7 // Not supported
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for explicit numbering since enum values are already in correct order

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

Comment on lines 224 to 231
MAX17616_STATUS_BIT_STARTUP = 0, // Not supported
MAX17616_STATUS_BIT_OTHER = 1, // Not supported
MAX17616_STATUS_BIT_FANS = 2, // Not supported
MAX17616_STATUS_BIT_POWER_GOOD = 3, // Not supported
MAX17616_STATUS_BIT_MFR = 4, // Manufacturer specific Fault
MAX17616_STATUS_BIT_INPUT = 5, // Input V, I, or P Fault
MAX17616_STATUS_BIT_IOUT_POUT = 6, // Output current or power Fault
MAX17616_STATUS_BIT_VOUT = 7 // Output voltage Fault
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Fixed in latest commit.

Copy link
Contributor

@amiclaus amiclaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good overall, some inline comments.

uint8_t *data, size_t nbytes)
{
int ret;
uint8_t rxbuf[nbytes + 2];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use a fixed max buffer size/dynamic allocation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed buffer size allocation to 256

{
uint16_t raw_value;
int ret;
float vin, iout;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

float values are discouraged for no-os drivers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced all float instances with uint32_t and adjusted calculations


case MAX17616_POWER:
/* Calculate power from voltage and current: P = V × I */
ret = max17616_read_value(dev, MAX17616_VOUT, &vin);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we store vout or vin here? if vout then use an appropriate naming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used correct parameter

if (!iio_desc)
return -ENODEV;

if (iio_desc->iio_dev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iio_desc->max17616_dev ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected the struct member to validate

MAX17616_VIN, /* Input voltage in volts */
MAX17616_VOUT, /* Output voltage in volt */
MAX17616_IOUT, /* Output current in amps */
MAX17616_TEMP, /* Temperature in degreesCelsius */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: degrees Celsius

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Placed a space in between

return -EINVAL;

return max17616_write_byte(dev, MAX17616_CMD(MAX17616_SET_ISTART_RATIO),
(uint8_t)istart_ratio);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any istart_ratio value is valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided the default values as per datasheet in the initialization

return -EINVAL;

/* Combine voltage selection (bits 4:2) and PGOOD threshold (bits 1:0) */
reg_value = ((uint8_t)voltage << 2) | (uint8_t)threshold;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using field preparation functions + macros.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used no-OS field preparation macros

@cjones-adi
Copy link
Contributor Author

V12

  • Rebased to latest main
  • Addressed PR comments (used no-OS bit field preparation macros, replaced float with uint32_t, provide manufacturer-specific configuration initialization values, corrected the IIO struct member to validate device removal, etc.)

* Since R = -1 for all MAX17616 coefficients:
* X_milli = ((Y x 10 - b) x 1000) / m
*/
static float max17616_direct_to_milliunit(uint16_t raw_value,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why float if it returns an int32_t ?

Copy link
Contributor Author

@cjones-adi cjones-adi Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. This was overlooked. Changed function return with proper data type.

test_expected_read_length = 2;
no_os_get_unaligned_le16_ExpectAndReturn(response_data, 0x3040);

int result = max17616_read_value(&device, MAX17616_VIN, &value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function expects an int32_t not a float.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated test variable data type in this test case.

no_os_i2c_read_IgnoreAndReturn(0);
no_os_get_unaligned_le16_IgnoreAndReturn(0x2000); // IOUT value

result = max17616_read_value(&device, MAX17616_POWER, &value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated test variable data type in this test case.

float value = 0.0f;

// Test with NULL device
int result = max17616_read_value(NULL, MAX17616_VIN, &value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same. please check all occurences where u pass float to a function that expects int32_t

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated test variable data type in this test case. Also updated lines 1317, 1325, 1333, and 1524.

TEST_ASSERT_EQUAL_INT(-EINVAL, result);

// Test with NULL value pointer
result = max17616_read_value(&device, MAX17616_VIN, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated test variable data type in this test case.


/* Check reasonable ranges for valid values */
if (telemetry->valid_mask & 0x01) { /* VIN */
if (telemetry->vin_mv < 0 || telemetry->vin_mv > 1000) { /* 0-100V */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit unclear. the comment says 100V but your check is for 1000mV

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated stale test cases from float to int32_t conversion

}

if (telemetry->valid_mask & 0x08) { /* IOUT */
if (telemetry->iout_ma < 0 || telemetry->iout_ma > 1000) { /* 0-100A */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated stale test cases from float to int32_t conversion

}

if (telemetry->valid_mask & 0x02) { /* VOUT */
if (telemetry->vout_mv < 0 || telemetry->vout_mv > 500) { /* 0-50V */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated stale test cases from float to int32_t conversion

},
{
.name = "pout",
.ch_type = IIO_ALTVOLTAGE,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be IIO_POWER?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected channel type for power

struct max17616_iio_desc *iio_max17616 = (struct max17616_iio_desc *)device;
struct max17616_telemetry telemetry;
struct max17616_status status;
uint16_t raw_value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unused variable

@cjones-adi
Copy link
Contributor Author

V13

  • Rebased to latest main
  • Addressed PR comments (Update overlooked float function return and unit test cases from float to int32_t conversion, add NULL check on max17616_read_status_byte(), align local variable type qualifiers for power calculation, add missing iio write attribute for nominal voltage and pgood threshold channels, etc.)

Copy link
Contributor

@amiclaus amiclaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some comments on my side.

int max17616_remove(struct max17616_dev *dev)
{
int ret;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing NULL check for dev.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the NULL dev is pre-checked by the calling function (a valid dev is a requirement to call this function)

if (ret)
return ret;

*value_milliunit = (int32_t)(((uint64_t)(uint32_t)vout_mv *
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want here to cat to uint? was looking at max17616_read_telemetry_all which handles stuff with signed integers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at the max17616_read_telemetry_all, the calculation there needs to follow the cast to uint for proper protection against overflow (resulting to large negative values) calculation. changed calculation in max17616_read_telemetry_all instead

int max17616_verify_manufacturer_id(struct max17616_dev *dev)
{
uint8_t mfr_id[8];
int ret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing NULL validation for dev throughout multiple functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the NULL dev is pre-checked in the init (which is the only function calling these static functions)

#include <stdbool.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include <stdio.h> seems to be redundant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true. thanks. removed #include <stdio.h>

memset(telemetry, 0, sizeof(struct max17616_telemetry));

ret = max17616_read_value(dev, MAX17616_VIN, &telemetry->vin_mv);
if (ret == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!ret) for all occurences.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retained (ret == 0) for clarity. it indicates that the function seeks for success. it has become relatively important with the related comment on basic_example.c and corresponding changes

int value;
int ret;

ret = sscanf(buf, "%d", &value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no range validation for value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great observation. thanks. added the validation of values range in the driver side so as not be repeated here or other application call (DRY principle).

* @param priv - Private data identifying which global attribute to write
* @return Number of bytes written on success, negative error code otherwise
*/
STATIC int max17616_iio_write_global_attr(void *device, char *buf, uint32_t len,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we return len in this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, i agree. let's follow the write convention call of returning len on a successful write operation and negative values for error.

if (byte_count != nbytes)
return -EMSGSIZE;

memcpy(data, &rxbuf[1], byte_count);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

include string.h?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it unintentionally not flagged perhaps due to other headers including it but i've explicitly added string.h for correctness

/* Read telemetry */
ret = max17616_read_telemetry_all(max17616_dev, &telemetry);
if (ret)
pr_err("Failed to read telemetry: %d\n\r", ret);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should somehow go to exit: for proper cleanup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking at max17616_read_telemetry_all, it doesn't really return error worthy of discontinuing the loop. modified the max17616_read_telemetry_all to catch read operation errors so it would warrant an exit and clean up here. great observations. thanks.

};

enum max17616_status_byte {
MAX17616_STATUS_BIT_GENERAL, // Not supported
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's be consistent with comments /* */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree. standardized comments.

@cjones-adi
Copy link
Contributor Author

V14

  • Rebased to latest main
  • Addressed PR comments (double check missing and unused headers, improve max17616_read_telemetry_all, return len upon successful write to follow Linux's sys write convention, add validation of ranges for enum type parameters, etc.)

Copy link
Contributor

@amiclaus amiclaus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor comments, otherwise lgtm.

int max17616_send_byte(struct max17616_dev *dev, uint8_t cmd)
{
uint8_t tx_buf[2] = {0};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dev null check missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added dev null check

uint8_t rx_buf[2];
uint8_t tx_buf[2] = {0};
tx_buf[0] = cmd;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: dev null check missing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added dev null check

int ret;

ret = no_os_i2c_remove(dev->i2c_desc);
if (ret)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ignore error here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore error on i2c removal

Add header and source files for max17616 driver.

Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
Add README.rst documentation file for max17616 alongside other
documentation related files.

Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
Add project files for both basic and IIO examples for max17616.

Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
Add README.rst documentation file for project alongside other
documentation related files.

Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
Add unit test files for the max17616 driver.

Signed-off-by: Carlos Jones <carlosjr.jones@analog.com>
@cjones-adi
Copy link
Contributor Author

V15

  • Rebased to latest main
  • Addressed PR comments (add dev null check on read byte and send byte, ignore error on i2c removal)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants