initial commit, reads dev id
Diff: MAX8614X_agc.cpp
- Revision:
- 5:1f7b8cb07e26
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MAX8614X_agc.cpp Fri Aug 17 05:35:58 2018 +0000 @@ -0,0 +1,509 @@ +/******************************************************************************* +* Author: Ismail Kose, Ismail.Kose@maximintegrated.com +* Copyright (C) 2016 Maxim Integrated Products, Inc., All Rights Reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES +* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +* Except as contained in this notice, the name of Maxim Integrated +* Products, Inc. shall not be used except as stated in the Maxim Integrated +* Products, Inc. Branding Policy. +* +* The mere transfer of this software does not imply any licenses +* of trade secrets, proprietary technology, copyrights, patents, +* trademarks, maskwork rights, or any other form of intellectual +* property whatsoever. Maxim Integrated Products, Inc. retains all +* ownership rights. +******************************************************************************* +*/ + +#include "MAX8614X.h" +#include <errno.h> + +#define pr_err(fmt, args...) if(1) printf(fmt " (%s:%d)\n", ##args, __func__, __LINE__) +#define pr_info(fmt, args...) if(1) printf(fmt " (%s:%d)\n", ##args, __func__, __LINE__) +#define pr_debug(fmt, args...) if(0) printf(fmt " (%s:%d)\n", ##args, __func__, __LINE__) + +#define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) + +#define ILLEGAL_OUTPUT_POINTER 1 +#define ILLEGAL_DIODE_OUTPUT_MIN_MAX_PAIR 2 +#define ILLEGAL_LED_SETTING_MIN_MAX_PAIR 3 +#define CONSTRAINT_VIOLATION 4 + +#define MAX8614X_LED_DRIVE_CURRENT_FULL_SCALE \ + (MAX8614X_MAX_LED_DRIVE_CURRENT - MAX8614X_MIN_LED_DRIVE_CURRENT) + +#define MAX8614X_AGC_DEFAULT_LED_OUT_RANGE 15 +#define MAX8614X_AGC_DEFAULT_CORRECTION_COEFF 50 +#define MAX8614X_AGC_DEFAULT_SENSITIVITY_PERCENT 10 +#define MAX8614X_AGC_DEFAULT_NUM_SAMPLES_TO_AVG 25 + +#define MAX8614X_PROX_THRESHOLD_1 10000 +#define MAX8614X_PROX_THRESHOLD_2 40000 +#define MAX8614X_PROX_DEBOUNCE_SPS 2 +#define MAX8614X_DAQ_DEBOUNCE_SPS 20 + +#define MAX8614X_DEFAULT_DAQ_LED_CURRENT_1 40000 +#define MAX8614X_DEFAULT_DAQ_LED_CURRENT_2 40000 +#define MAX8614X_DEFAULT_DAQ_LED_CURRENT_3 40000 +#define MAX8614X_DEFAULT_PROX_LED_CURRENT_1 10000 +#define MAX8614X_DEFAULT_PROX_LED_CURRENT_2 0 +#define MAX8614X_DEFAULT_PROX_LED_CURRENT_3 0 + + +#define MAX8614X_MIN_LED_DRIVE_CURRENT 0 +#define MAX8614X_MAX_LED_DRIVE_CURRENT 60000 + +#define MAX8614X_MAX_PPG_DIODE_VAL ((1 << 19) - 1) +#define MAX8614X_MIN_PPG_DIODE_VAL 0 + +#define MAX8614X_DEFAULT_CURRENT1 0x30 +#define MAX8614X_DEFAULT_CURRENT2 0 +#define MAX8614X_DEFAULT_CURRENT3 0 + + +int MAX8614X::max8614x_update_led_range( + int new_range, uint8_t led_num, + union led_range *led_range_settings) +{ + int old_range; + Registers reg_addr; + + switch (led_num) { + case LED_1: + old_range = led_range_settings->led1; + led_range_settings->led1 = new_range; + reg_addr = MAX8614X_LED_RANGE1_REG; + break; + case LED_2: + old_range = led_range_settings->led2; + led_range_settings->led2 = new_range; + reg_addr = MAX8614X_LED_RANGE1_REG; + break; + case LED_3: + old_range = led_range_settings->led3; + led_range_settings->led3 = new_range; + reg_addr = MAX8614X_LED_RANGE1_REG; + break; + case LED_4: + old_range = led_range_settings->led4; + led_range_settings->led4 = new_range; + reg_addr = MAX8614X_LED_RANGE2_REG; + break; + case LED_5: + old_range = led_range_settings->led5; + led_range_settings->led5 = new_range; + reg_addr = MAX8614X_LED_RANGE2_REG; + break; + case LED_6: + old_range = led_range_settings->led6; + led_range_settings->led6 = new_range; + reg_addr = MAX8614X_LED_RANGE2_REG; + break; + + default: + return -EINVAL; + } + + if (old_range == new_range) + return 0; + + return writeRegister( reg_addr, + led_range_settings->val[led_num < LED_4 ? 0 : 1]); +} + +int MAX8614X::max8614x_update_led_current( + union led_range *led_range_settings, + int led_new_val, + max8614x_led_t led_num) +{ + int ret = 0; + Registers led_current_reg_addr; + int led_range; + uint8_t led_current_reg_val; + int led_range_index = led_new_val / 25000; + const int led_range_steps[] = { + LED_RANGE_STEP_25uA, + LED_RANGE_STEP_50uA, + LED_RANGE_STEP_75uA, + LED_RANGE_STEP_100uA, + LED_RANGE_STEP_100uA, /* For led current greater than 100uA */ + }; + + switch(led_num) { + case LED_1: + led_current_reg_addr = MAX8614X_LED1_PA_REG; + break; + case LED_2: + led_current_reg_addr = MAX8614X_LED2_PA_REG; + break; + case LED_3: + led_current_reg_addr = MAX8614X_LED3_PA_REG; + break; + case LED_4: + led_current_reg_addr = MAX8614X_LED4_PA_REG; + break; + case LED_5: + led_current_reg_addr = MAX8614X_LED5_PA_REG; + break; + case LED_6: + led_current_reg_addr = MAX8614X_LED6_PA_REG; + break; + default: + pr_err("Invalid led number: %d\n", led_num); + return -EINVAL; + } + + if (led_new_val < MAX8614X_MIN_LED_DRIVE_CURRENT + || led_new_val > MAX8614X_MAX_LED_DRIVE_CURRENT) { + pr_err("Invalid led value: %d\n", led_new_val); + return -EINVAL; + } + + led_current_reg_val = led_new_val / led_range_steps[led_range_index]; + + pr_debug("Updating LED%d current to %d. led_rge_idx: %d, reg_val: %.2X", + led_num, led_new_val, led_range_index, led_current_reg_val); + + ret = writeRegister(led_current_reg_addr, led_current_reg_val); + if (ret < 0) + return ret; + + + led_range = led_range_index; + pr_debug("Updating LED%d range to %d.", led_num, led_range); + if (led_range > 3) + led_range = 3; + ret = max8614x_update_led_range( led_range, led_num, led_range_settings); + if (ret < 0) + return ret; + return ret; +} + +int32_t agc_adj_calculator( + int32_t *change_by_percent_of_range, + int32_t *change_by_percent_of_current_setting, + int32_t *change_led_by_absolute_count, + int32_t *set_led_to_absolute_count, + int32_t target_percent_of_range, + int32_t correction_coefficient, + int32_t allowed_error_in_percentage, + int32_t current_average, + int32_t number_of_samples_averaged, + int32_t led_drive_current_value) +{ + int32_t current_percent_of_range = 0; + int32_t delta = 0; + int32_t desired_delta = 0; + int32_t current_power_percent = 0; + + if (change_by_percent_of_range == 0 + || change_by_percent_of_current_setting == 0 + || change_led_by_absolute_count == 0 + || set_led_to_absolute_count == 0) + return ILLEGAL_OUTPUT_POINTER; + + if (target_percent_of_range > 90 || target_percent_of_range < 10) + return CONSTRAINT_VIOLATION; + + if (correction_coefficient > 100 || correction_coefficient < 0) + return CONSTRAINT_VIOLATION; + + if (allowed_error_in_percentage > 100 + || allowed_error_in_percentage < 0) + return CONSTRAINT_VIOLATION; + +#if ((MAX8614X_MAX_PPG_DIODE_VAL - MAX8614X_MIN_PPG_DIODE_VAL) <= 0 \ + || (MAX8614X_MAX_PPG_DIODE_VAL < 0) || (MAX8614X_MIN_PPG_DIODE_VAL < 0)) + #error "Illegal diode Min/Max Pair" +#endif + +#if ((MAX8614X_MAX_LED_DRIVE_CURRENT - MAX8614X_MIN_LED_DRIVE_CURRENT) <= 0 \ + || (MAX8614X_MAX_LED_DRIVE_CURRENT < 0) || (MAX8614X_MIN_LED_DRIVE_CURRENT < 0)) + #error "Illegal LED Min/Max current Pair" +#endif + + if (led_drive_current_value > MAX8614X_MAX_LED_DRIVE_CURRENT + || led_drive_current_value < MAX8614X_MIN_LED_DRIVE_CURRENT) + return CONSTRAINT_VIOLATION; + + if (current_average < MAX8614X_MIN_PPG_DIODE_VAL + || current_average > MAX8614X_MAX_PPG_DIODE_VAL) + return CONSTRAINT_VIOLATION; + + current_percent_of_range = 100 * + (current_average - MAX8614X_MIN_PPG_DIODE_VAL) / + (MAX8614X_MAX_PPG_DIODE_VAL - MAX8614X_MIN_PPG_DIODE_VAL) ; + + delta = current_percent_of_range - target_percent_of_range; + delta = delta * correction_coefficient / 100; + + if (delta > -allowed_error_in_percentage + && delta < allowed_error_in_percentage) { + *change_by_percent_of_range = 0; + *change_by_percent_of_current_setting = 0; + *change_led_by_absolute_count = 0; + *set_led_to_absolute_count = led_drive_current_value; + return 0; + } + + current_power_percent = 100 * + (led_drive_current_value - MAX8614X_MIN_LED_DRIVE_CURRENT) / + (MAX8614X_MAX_LED_DRIVE_CURRENT - MAX8614X_MIN_LED_DRIVE_CURRENT); + if (delta < 0) + desired_delta = -delta * (100 - current_power_percent) / + (100 - current_percent_of_range); + + if (delta > 0) + desired_delta = -delta * (current_power_percent) + / (current_percent_of_range); + + *change_by_percent_of_range = desired_delta; + + *change_led_by_absolute_count = (desired_delta + * MAX8614X_LED_DRIVE_CURRENT_FULL_SCALE / 100); + *change_by_percent_of_current_setting = + (*change_led_by_absolute_count * 100) + / (led_drive_current_value); + *set_led_to_absolute_count = led_drive_current_value + + *change_led_by_absolute_count; + + //If we are saturated, cut power in half + if (current_percent_of_range >= 100) + { + *change_by_percent_of_range = -100; //Unknown, set fake value + *change_by_percent_of_current_setting = -50; + *change_led_by_absolute_count = 0 - (led_drive_current_value / 2); + *set_led_to_absolute_count = led_drive_current_value / 2; + } + + return 0; +} + +void MAX8614X::ppg_auto_gain_ctrl( + struct led_control *led_ctrl, + uint32_t sample_cnt, int diode_data, max8614x_led_t led_num) +{ + int ret; + int diode_avg; + + if (led_num > LED_3) /* TODO: why3? */ + return; + + led_ctrl->diode_sum[led_num] += diode_data; + if (sample_cnt % led_ctrl->agc_min_num_samples == 0) { + diode_avg = led_ctrl->diode_sum[led_num] + / led_ctrl->agc_min_num_samples; + led_ctrl->diode_sum[led_num] = 0; + } else + return; + + ret = agc_adj_calculator( + &led_ctrl->change_by_percent_of_range[led_num], + &led_ctrl->change_by_percent_of_current_setting[led_num], + &led_ctrl->change_led_by_absolute_count[led_num], + &led_ctrl->led_current[led_num], + led_ctrl->agc_led_out_percent, + led_ctrl->agc_corr_coeff, + led_ctrl->agc_sensitivity_percent, + diode_avg, + led_ctrl->agc_min_num_samples, + led_ctrl->led_current[led_num]); + if (ret) + return; + + if (led_ctrl->change_led_by_absolute_count[led_num] == 0) + return; + + ret = max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[led_num], led_num); + if (ret < 0) + pr_err("%s failed", __func__); + return; +} + +void MAX8614X::max8614x_agc_handler(struct led_control *led_ctrl, + int *samples) +{ + static int ret = -1; + + if (!led_ctrl->agc_is_enabled) + return; + + led_ctrl->sample_cnt++; + ret = led_control_sm(led_ctrl, + samples[DATA_TYPE_PPG1_LEDC1], + led_ctrl->lpm_is_enabled); + + if (ret == LED_DATA_ACQ) { + ppg_auto_gain_ctrl(led_ctrl, + led_ctrl->sample_cnt, + samples[DATA_TYPE_PPG1_LEDC1], + LED_1); + ppg_auto_gain_ctrl(led_ctrl, + led_ctrl->sample_cnt, + samples[DATA_TYPE_PPG1_LEDC2], + LED_2); + ppg_auto_gain_ctrl(led_ctrl, + led_ctrl->sample_cnt, + samples[DATA_TYPE_PPG1_LEDC3], + LED_3); + } + + return; +} + +int MAX8614X::led_prox_init(struct led_control *led_ctrl, char lpm) +{ + int ret; + const RegisterMap low_pm_settings[] = { + { MAX8614X_PPG_CFG1_REG, MAX8614X_PPG_LED_PW_115_2_US_MASK // PPG_LED_PW = 3 (115.2us) + | MAX8614X_PPG1_ADC_RGE_32768_MASK // PPG1_ADC_RGE = 3(32768nA) + | MAX8614X_PPG2_ADC_RGE_32768_MASK }, // PPG2_ADC_RGE = 3(32768nA) + { MAX8614X_PPG_CFG2_REG, MAX8614X_PPG_SR_25_SPS}, + { MAX8614X_INT_ENABLE1_REG, MAX8614X_INT1_EN_DATA_RDY_MASK }, + }; + + led_ctrl->led_current[LED_1] = MAX8614X_DEFAULT_PROX_LED_CURRENT_1; + ret = max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_1], LED_1); + + led_ctrl->led_current[LED_2] = MAX8614X_DEFAULT_PROX_LED_CURRENT_2; + ret |= max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_2], LED_2); + + led_ctrl->led_current[LED_3] = MAX8614X_DEFAULT_PROX_LED_CURRENT_3; + ret |= max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_3], LED_3); + + if (lpm) + ret |= writeBlock(low_pm_settings, + ARRAY_SIZE(low_pm_settings)); + return ret; +} + +int MAX8614X::led_daq_init(struct led_control *led_ctrl, char lpm) +{ + int ret; + const RegisterMap non_lpm_settings[] = { + { MAX8614X_PPG_CFG1_REG, MAX8614X_PPG_LED_PW_115_2_US_MASK // PPG_LED_PW = 3 (115.2us) + | MAX8614X_PPG1_ADC_RGE_32768_MASK // PPG1_ADC_RGE = 3(32768nA) + | MAX8614X_PPG2_ADC_RGE_32768_MASK }, // PPG2_ADC_RGE = 3(32768nA) + { MAX8614X_PPG_CFG2_REG, MAX8614X_PPG_SR_100_SPS}, + { MAX8614X_INT_ENABLE1_REG, MAX8614X_INT1_EN_A_FULL_MASK }, + }; + + led_ctrl->led_current[LED_1] = MAX8614X_DEFAULT_DAQ_LED_CURRENT_1; + ret = max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_1], LED_1); + + led_ctrl->led_current[LED_2] = MAX8614X_DEFAULT_DAQ_LED_CURRENT_2; + ret |= max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_2], LED_2); + + led_ctrl->led_current[LED_3] = MAX8614X_DEFAULT_DAQ_LED_CURRENT_3; + ret |= max8614x_update_led_current(&led_ctrl->led_range_settings, + led_ctrl->led_current[LED_3], LED_3); + + if (lpm) + ret |= writeBlock(non_lpm_settings, + ARRAY_SIZE(non_lpm_settings)); + + return ret; +} + +int MAX8614X::led_control_sm(struct led_control *led_ctrl, int diode_data, char lpm) +{ + int ret = led_ctrl->state; + int avg = 0; + + led_ctrl->prox_sample_cnt++; + led_ctrl->prox_sum += diode_data; + + switch (led_ctrl->state) { + case LED_PROX: + if (led_ctrl->prox_sample_cnt % MAX8614X_PROX_DEBOUNCE_SPS != 0) + break; + + avg = led_ctrl->prox_sum / MAX8614X_PROX_DEBOUNCE_SPS; + if (avg >= MAX8614X_PROX_THRESHOLD_1) { + led_ctrl->state = LED_DATA_ACQ; + ret = led_daq_init(led_ctrl, lpm); + led_ctrl->prox_sample_cnt = 0; + } + led_ctrl->prox_sum = 0; + break; + + case LED_DATA_ACQ: + if (led_ctrl->prox_sample_cnt % MAX8614X_DAQ_DEBOUNCE_SPS != 0) + break; + + avg = led_ctrl->prox_sum / MAX8614X_DAQ_DEBOUNCE_SPS; + if (avg <= MAX8614X_PROX_THRESHOLD_2) { + led_ctrl->state = LED_PROX; + ret = led_prox_init(led_ctrl, lpm); + led_ctrl->prox_sample_cnt = 0; + } + led_ctrl->prox_sum = 0; + break; + + default: + led_ctrl->state = LED_PROX; + led_ctrl->prox_sum = 0; + led_ctrl->prox_sample_cnt = 0; + return -EINVAL; + } + + return ret; +} + +void MAX8614X::led_control_reset(struct led_control *led_ctrl) +{ + led_ctrl->led_current[LED_1] = led_ctrl->default_current[LED_1]; + led_ctrl->led_current[LED_2] = led_ctrl->default_current[LED_2]; + led_ctrl->led_current[LED_3] = led_ctrl->default_current[LED_3]; + + memset(led_ctrl->change_by_percent_of_range, 0, + sizeof(led_ctrl->change_by_percent_of_range)); + memset(led_ctrl->change_by_percent_of_current_setting, 0, + sizeof(led_ctrl->change_by_percent_of_range)); + memset(led_ctrl->change_led_by_absolute_count, 0, + sizeof(led_ctrl->change_by_percent_of_range)); + memset(led_ctrl->diode_sum, 0, sizeof(led_ctrl->diode_sum)); + + led_ctrl->agc_is_enabled = 1; + led_ctrl->prox_sum = 0; + led_ctrl->prox_sample_cnt = 0; + led_ctrl->sample_cnt = -1; + led_ctrl->state = LED_PROX; +} + +void MAX8614X::led_control_init(struct led_control *led_ctrl) +{ + memset(led_ctrl, 0, sizeof(struct led_control)); + + led_ctrl->default_current[LED_1] = MAX8614X_DEFAULT_CURRENT1; + led_ctrl->default_current[LED_2] = MAX8614X_DEFAULT_CURRENT2; + led_ctrl->default_current[LED_3] = MAX8614X_DEFAULT_CURRENT3; + led_ctrl->agc_led_out_percent = MAX8614X_AGC_DEFAULT_LED_OUT_RANGE; + led_ctrl->agc_corr_coeff = MAX8614X_AGC_DEFAULT_CORRECTION_COEFF; + led_ctrl->agc_min_num_samples = MAX8614X_AGC_DEFAULT_NUM_SAMPLES_TO_AVG; + led_ctrl->agc_sensitivity_percent = MAX8614X_AGC_DEFAULT_SENSITIVITY_PERCENT; +} +