742 lines
26 KiB
C
742 lines
26 KiB
C
/* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
/* The NVS = NVidia Sensor framework */
|
|
/* This common NVS on-change module allows, along with the NVS KIF (Kernel
|
|
* InterFace) common module, an on-change driver to offload the code
|
|
* interacting with the kernel and handling the on-change reporting specifics,
|
|
* and just have code that interacts with the HW.
|
|
* An on-change sensor is defined by the Android sensor specification. This
|
|
* module adheres to that specification with regard to operation and behavior,
|
|
* but also adds a superset of features.
|
|
* The commonality between this module and the NVS HW driver is the
|
|
* nvs_on_change structure. It is expected that the NVS on-change driver will:
|
|
* - call nvs_on_change_enable when the device is enabled for initialization.
|
|
* - read the HW and place the value in nvs_on_change.hw
|
|
* - call nvs_on_change_read
|
|
* - depending on the nvs_on_change_read return value:
|
|
* - -1 = poll HW using nvs_on_change.poll_period_ms period.
|
|
* - 0 = if interrupt driven, do nothing or resume regular polling
|
|
* - 1 = set new thresholds using the nvs_on_change.hw_thresh_lo/hi
|
|
* Reporting the on-change value is handled within this module.
|
|
* See nvs_on_change.h for nvs_on_change structure details.
|
|
*/
|
|
/* NVS on-change drivers can be configured for binary output. If the max_range
|
|
* and resolution settings in the device tree is set for 1.0, the driver
|
|
* will configure the rest of the settings for binary reporting. For example,
|
|
* the Android specification allows proximity to be reported as 1 for
|
|
* "far away" and 0 for "near".
|
|
* If on-change binary output is disabled, the driver can use a number of NVS
|
|
* mechanisms to send real-world values:
|
|
* - scale_float_en
|
|
* - calibration
|
|
* - and just plain 'ol raw data
|
|
* See nvs_on_change.h for nvs_on_change features.
|
|
*/
|
|
/* When using the threshold sysfs attributes, keep in mind that all threshold
|
|
* values are in HW units. This allows the thresholds to automatically scale
|
|
* due to any parameter modifications such as resolution.
|
|
*/
|
|
/* If the NVS on-change driver is not configured for binary output, then
|
|
* there are few mechanisms for reporting float data:
|
|
* Method 1
|
|
* This is required if the driver is using dynamic resolution since the
|
|
* resolution cannot be read by the NVS HAL on every data value read due to
|
|
* buffering. Instead, this mechanism allows floating point to be calculated
|
|
* here in the kernel by multiplying up to an integer the floating point's
|
|
* significant amount. The scale.fval must be a 10 base value,
|
|
* e.g. 0.1, 0.01, ... 0.000001, etc. as the significant amount. The NVS HAL
|
|
* will then convert the value to float by multiplying the integer float-data
|
|
* with scale. To enable this feature, set the scale_float_en member in the
|
|
* nvs_on_change structure. The following will be needed for calibration.
|
|
* Method 2
|
|
* The NVS HAL will use the scale and offset sysfs attributes to modify the
|
|
* data using the following formula: (data * scale) + offset
|
|
* A scale value of 0 disables scale.
|
|
* A scale value of 1 puts the NVS HAL into calibration mode where the scale
|
|
* and offset are read everytime the data is read to allow realtime calibration
|
|
* of the scale and offset values to be used in the device tree parameters.
|
|
* Keep in mind the data is buffered but the NVS HAL will display the data and
|
|
* scale/offset parameters in the log. See calibration steps below if needed.
|
|
* Method 3
|
|
* This method uses interpolation and requires a low and high uncalibrated
|
|
* value along with the corresponding low and high calibrated values. The
|
|
* uncalibrated values are what is read from the sensor in the steps below.
|
|
* The corresponding calibrated values are what the correct value should be.
|
|
* All values are programmed into the device tree settings.
|
|
* Calibration steps:
|
|
* 1. Read scale sysfs attribute. This value will need to be written back.
|
|
* 2. Disable device.
|
|
* 3. Write 1 to the scale sysfs attribute.
|
|
* 4. Enable device.
|
|
* 5. The NVS HAL will announce in the log that calibration mode is enabled and
|
|
* display the data along with the scale and offset parameters applied.
|
|
* 6. Write the scale value read in step 1 back to the scale sysfs attribute.
|
|
* 7. Put the device into a state where the data read is a low value.
|
|
* 8. Note the values displayed in the log. Separately measure the actual
|
|
* value. The value from the sensor will be the uncalibrated value and the
|
|
* separately measured value will be the calibrated value for the current
|
|
* state (low or high values).
|
|
* 9. Put the device into a state where the data read is a high value.
|
|
* 10. Repeat step 8.
|
|
* 11. Enter the values in the device tree settings for the device. Both
|
|
* calibrated and uncalibrated values will be the values before scale and
|
|
* offset are applied.
|
|
* The on-change sensor has the following device tree parameters for this:
|
|
* on_change_uncalibrated_lo
|
|
* on_change_calibrated_lo
|
|
* on_change_uncalibrated_hi
|
|
* on_change_calibrated_hi
|
|
*
|
|
* An NVS on-change driver may support a simplified version of method 2 that
|
|
* can be used in realtime:
|
|
* At step 8, write the calibrated value to the in_on_change_threshold_low
|
|
* attribute. When in calibration mode this value will be written to the
|
|
* on_change_calibrated_lo and the current on_change written to
|
|
* on_change_uncalibrated_lo internal to the driver.
|
|
* If this is after step 9, then use the in_on_change_threshold_high
|
|
* attribute.
|
|
* Note that the calibrated value must be the value before the scale and offset
|
|
* is applied. For example, if the calibrated on_change reading is 123.4 cm,
|
|
* and the in_on_change_scale is normally 0.01, then the value entered is 12340
|
|
* which will be 123.4 cm when the scale is applied at the HAL layer.
|
|
* To confirm the realtime values and see what the driver used for uncalibrated
|
|
* values, do the following at the adb prompt in the driver space:
|
|
* # echo 5 > nvs
|
|
* # cat nvs
|
|
* This will be a partial dump of the sensor's configuration structure that
|
|
* will show the calibrated and uncalibrated values. For example:
|
|
* ...
|
|
* uncal_lo=1
|
|
* uncal_hi=96346
|
|
* cal_lo=230
|
|
* cal_hi=1888000
|
|
* thresh_lo=10
|
|
* thresh_hi=10
|
|
* ...
|
|
* If the thresholds have changed instead of the calibration settings, then
|
|
* the driver doesn't support this feature.
|
|
* In order to display raw values, interpolation, that uses the calibration
|
|
* values, is not executed by the driver when in calibration mode, so to test,
|
|
* disable and reenable the device to exit calibration mode and test the new
|
|
* calibration values.
|
|
*
|
|
* Method 2 can only be used if dynamic resolution is not used by the HW
|
|
* driver. The data passed up to the HAL is the HW value read so that the HAL
|
|
* can multiply the HW value with the scale (resolution).
|
|
* As a baseline, scale would be the same value as the static resolution.
|
|
* Method 2:
|
|
* 1. Disable device.
|
|
* 2. Write 1 to the scale sysfs attribute.
|
|
* 3. Enable device.
|
|
* 4. The NVS HAL will announce in the log that calibration mode is enabled and
|
|
* display the data along with the scale and offset parameters applied.
|
|
* 5. Write to scale and offset sysfs attributes as needed to get the data
|
|
* modified as desired.
|
|
* 6. Disabling the device disables calibration mode.
|
|
* 7. Set the new scale and offset parameters in the device tree:
|
|
* on_change_scale_ival = the integer value of the scale.
|
|
* on_change_scale_fval = the floating value of the scale.
|
|
* on_change_offset_ival = the integer value of the offset.
|
|
* on_change_offset_fval = the floating value of the offset.
|
|
* The values are in the NVS_FLOAT_SIGNIFICANCE_ format (see nvs.h).
|
|
*/
|
|
/* If the NVS on-change driver is configured for binary output, then
|
|
* interpolation is not used and the thresholds are used to trigger either the
|
|
* 0 or 1 output. The following is an example of how to calibrate a proximity
|
|
* sensor using the thresholds:
|
|
* 1. Disable device.
|
|
* 2. Write 1 to the scale sysfs attribute.
|
|
* 3. Enable device.
|
|
* 4. The NVS HAL will announce in the log that calibration mode is enabled and
|
|
* display the HW on-change data.
|
|
* 5. Move an object (your hand) through the on-change range. Note the HW
|
|
* value when the object is at a point that the output should be 0. This
|
|
* will be the high threshold value. Move the object away from the sensor
|
|
* and note the HW value where the output should change to 1. This will be
|
|
* the low threshold value.
|
|
* NOTE: Proximity typically works by reading the reflected IR light from an
|
|
* LED. The more light reflected, the higher the HW value and the closer
|
|
* the object is. Because of this, the thresholds appear to be reversed
|
|
* to the output, but keep in mind, the thresholds are HW based, so low
|
|
* threshold means low HW value regardless of the actual output.
|
|
* NOTE: If greater range is needed, modify the LED output strength if the
|
|
* on-change HW supports it. This will be a DT configuration option that
|
|
* is specific to the driver and HW.
|
|
* 6. Enter the threshold values in the device tree settings for the device.
|
|
* The on-change sensor has the following device tree parameters for this:
|
|
* on_change_threshold_lo
|
|
* on_change_threshold_hi
|
|
* 7. Be sure to add the device tree attribute:
|
|
* <sensor_name>_thresholds_absolute_en = <1>;
|
|
* This will configure the thresholds to use absolute values,
|
|
* (OC_THRESHOLDS_ABSOLUTE).
|
|
*/
|
|
/* If the NVS on-change driver is not configured for OC_THRESHOLDS_ABSOLUTE,
|
|
* then the thresholds are used for hysterysis. The threshold settings are HW
|
|
* based and allow a window around the last reported on_change HW value. For
|
|
* example, if the low threshold is set to 10 and the high threshold set to 20,
|
|
* if the on_change HW value is 100, the on_change won't be reported again
|
|
* until the on_change HW value is either < 90 or > than 120.
|
|
* The low/high threshold values are typically the same, but they can be
|
|
* configured so that on_change changes at a different rate based on the
|
|
* direction of change.
|
|
* Use the calibration methods for a steady output of data to get an idea of
|
|
* the debounce desired.
|
|
* NOTE: If both configuration thresholds are 0, then thresholds are disabled.
|
|
* NOTE: An NVS feature is the use of the report_count configuration variable,
|
|
* <sensor_name>_report_count in DT (see nvs.h). This allows additional
|
|
* reporting of on_change a set amount of times while still within the
|
|
* threshold window.
|
|
*/
|
|
/* If the NVS on-change driver is configured for binary output, then the
|
|
* thresholds are absolute HW values. If not configured for binary output,
|
|
* then the thresholds are relative HW values to set a trigger window around
|
|
* the last read on_change HW value.
|
|
*/
|
|
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/version.h>
|
|
#include <linux/nvs_on_change.h>
|
|
|
|
#define NVS_ON_CHANGE_VERSION (1)
|
|
#define NVS_FS_NANO NVS_FLOAT_SIGNIFICANCE_NANO
|
|
#define NVS_FS_MICRO NVS_FLOAT_SIGNIFICANCE_MICRO
|
|
|
|
|
|
ssize_t nvs_on_change_dbg(struct nvs_on_change *oc, char *buf, size_t size)
|
|
{
|
|
ssize_t t;
|
|
|
|
t = snprintf(buf, size, "NVS ON-CHANGE v.%u\n", NVS_ON_CHANGE_VERSION);
|
|
t += snprintf(buf + t, size - t, "timestamp=%lld\n",
|
|
oc->timestamp);
|
|
t += snprintf(buf + t, size - t, "timestamp_report=%lld\n",
|
|
oc->timestamp_report);
|
|
t += snprintf(buf + t, size - t, "on_change=%u\n", oc->on_change);
|
|
t += snprintf(buf + t, size - t, "hw=%u\n", oc->hw);
|
|
t += snprintf(buf + t, size - t, "hw_min=%x\n", oc->hw_min);
|
|
t += snprintf(buf + t, size - t, "hw_max=%x\n", oc->hw_max);
|
|
t += snprintf(buf + t, size - t, "hw_thresh_lo=%u\n",
|
|
oc->hw_thresh_lo);
|
|
t += snprintf(buf + t, size - t, "hw_thresh_hi=%u\n",
|
|
oc->hw_thresh_hi);
|
|
t += snprintf(buf + t, size - t, "hw_limit_lo=%x\n",
|
|
oc->hw_limit_lo);
|
|
t += snprintf(buf + t, size - t, "hw_limit_hi=%x\n",
|
|
oc->hw_limit_hi);
|
|
t += snprintf(buf + t, size - t, "thresh_valid_lo=%x\n",
|
|
oc->thresh_valid_lo);
|
|
t += snprintf(buf + t, size - t, "thresh_valid_hi=%x\n",
|
|
oc->thresh_valid_hi);
|
|
t += snprintf(buf + t, size - t, "reverse_range_en=%x\n",
|
|
oc->reverse_range_en);
|
|
t += snprintf(buf + t, size - t, "scale_float_en=%x\n",
|
|
oc->scale_float_en);
|
|
t += snprintf(buf + t, size - t, "binary_report_en=%x\n",
|
|
oc->binary_report_en);
|
|
if (oc->threshold < 0)
|
|
t += snprintf(buf + t, size - t, "OC_THRESHOLDS_DISABLE\n");
|
|
else if (oc->threshold > 0)
|
|
t += snprintf(buf + t, size - t, "OC_THRESHOLDS_ABSOLUTE\n");
|
|
else
|
|
t += snprintf(buf + t, size - t, "OC_THRESHOLDS_RELATIVE\n");
|
|
if (oc->calibration < 0)
|
|
t += snprintf(buf + t, size - t, "OC_CALIBRATION_DISABLE\n");
|
|
else if (oc->calibration > 0)
|
|
t += snprintf(buf + t, size - t, "OC_CALIBRATION_ENABLE\n");
|
|
else
|
|
t += snprintf(buf + t, size - t, "OC_CALIBRATION_DONE\n");
|
|
t += snprintf(buf + t, size - t, "poll_period_ms=%u\n",
|
|
oc->poll_period_ms);
|
|
t += snprintf(buf + t, size - t, "period_us=%u\n", oc->period_us);
|
|
t += snprintf(buf + t, size - t, "report_n=%u\n", oc->report_n);
|
|
return t;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_dbg);
|
|
|
|
ssize_t nvs_on_change_dbg_dt(struct nvs_on_change *oc, char *buf, size_t size,
|
|
const char *dev_name)
|
|
{
|
|
char str[256];
|
|
int ret;
|
|
ssize_t t = 0;
|
|
|
|
if (buf == NULL)
|
|
return -EINVAL;
|
|
|
|
if (dev_name == NULL)
|
|
dev_name = oc->cfg->name;
|
|
ret = snprintf(str, sizeof(str), "%s_thresholds_absolute_en",
|
|
dev_name);
|
|
if (ret > 0) {
|
|
t += snprintf(buf + t, size - t, "%s=%d\n",
|
|
str, oc->threshold);
|
|
}
|
|
return t;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_dbg_dt);
|
|
|
|
/**
|
|
* nvs_on_change_of_dt - called during system boot for
|
|
* configuration from device tree.
|
|
* @oc: the common structure between driver and common module.
|
|
* @dn: device node pointer.
|
|
* @dev_name: device name string. Typically a string to the
|
|
* Android sensor name.
|
|
*
|
|
* Returns 0 on success or a negative error code.
|
|
*
|
|
* Driver must initialize variables if no success.
|
|
*/
|
|
int nvs_on_change_of_dt(struct nvs_on_change *oc, const struct device_node *dn,
|
|
const char *dev_name)
|
|
{
|
|
s32 val;
|
|
char str[256];
|
|
int ret;
|
|
|
|
if (oc->cfg)
|
|
oc->cfg->flags |= SENSOR_FLAG_ON_CHANGE_MODE;
|
|
if (dn == NULL)
|
|
return -EINVAL;
|
|
|
|
val = oc->cfg->thresh_lo;
|
|
val |= oc->cfg->thresh_hi;
|
|
/* val > 0 ==> 0 (OC_THRESHOLDS_RELATIVE)
|
|
* val == 0 ==> -1 (OC_THRESHOLDS_DISABLE)
|
|
* val < 0 ==> 0 (OC_THRESHOLDS_RELATIVE)
|
|
*/
|
|
val = !!!val;
|
|
val = 0 - val;
|
|
if (dev_name == NULL)
|
|
dev_name = oc->cfg->name;
|
|
ret = snprintf(str, sizeof(str), "%s_thresholds_absolute_en", dev_name);
|
|
if (ret > 0)
|
|
of_property_read_s32(dn, str, &val);
|
|
if (val < 0)
|
|
oc->threshold = OC_THRESHOLDS_DISABLE;
|
|
else if (val > 0)
|
|
oc->threshold = OC_THRESHOLDS_ABSOLUTE;
|
|
else
|
|
oc->threshold = OC_THRESHOLDS_RELATIVE;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_of_dt);
|
|
|
|
static void nvs_on_change_interpolate(int x1, s64 x2, int x3,
|
|
int y1, u32 *y2, int y3)
|
|
{
|
|
s64 dividend;
|
|
s64 divisor;
|
|
|
|
/* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */
|
|
divisor = (x3 - x1);
|
|
if (!divisor) {
|
|
*y2 = (u32)x2;
|
|
return;
|
|
}
|
|
|
|
dividend = (x2 - x1) * (y3 - y1);
|
|
if (dividend < 0) {
|
|
dividend = abs(dividend);
|
|
do_div(dividend, divisor);
|
|
dividend = 0 - dividend;
|
|
} else {
|
|
do_div(dividend, divisor);
|
|
}
|
|
dividend += y1;
|
|
if (dividend < 0)
|
|
dividend = 0;
|
|
*y2 = (u32)dividend;
|
|
}
|
|
|
|
static int nvs_on_change_poll_period(struct nvs_on_change *oc, int ret,
|
|
unsigned int poll_period,
|
|
bool report_period_min)
|
|
{
|
|
if (report_period_min)
|
|
poll_period = oc->period_us;
|
|
if ((poll_period < oc->cfg->delay_us_min) || (oc->calibration ==
|
|
OC_CALIBRATION_ENABLE))
|
|
poll_period = oc->cfg->delay_us_min;
|
|
oc->poll_period_ms = poll_period / 1000;
|
|
if (oc->report_n || oc->calibration == OC_CALIBRATION_ENABLE)
|
|
ret = RET_POLL_NEXT; /* poll for next sample */
|
|
return ret;
|
|
}
|
|
|
|
static int nvs_on_change_push(struct nvs_on_change *oc, s32 on_change,
|
|
unsigned int poll_period, bool report_period_min)
|
|
{
|
|
int ret = RET_NO_CHANGE;
|
|
|
|
if (oc->report_n && report_period_min) {
|
|
oc->report_n--;
|
|
oc->timestamp_report = oc->timestamp;
|
|
oc->on_change = on_change;
|
|
oc->handler(oc->nvs_st, &oc->on_change,
|
|
oc->timestamp_report);
|
|
ret = RET_HW_UPDATE;
|
|
}
|
|
return nvs_on_change_poll_period(oc, ret, poll_period,
|
|
report_period_min);
|
|
}
|
|
|
|
static int nvs_on_change_calc(struct nvs_on_change *oc, s64 *on_change)
|
|
{
|
|
s64 calc;
|
|
u64 calc_i;
|
|
u64 calc_f;
|
|
|
|
if (oc->reverse_range_en)
|
|
/* reverse the value in the range */
|
|
calc = oc->hw_max - oc->hw;
|
|
else
|
|
calc = oc->hw;
|
|
if (oc->scale_float_en && oc->cfg->scale.fval) {
|
|
/* The mechanism below allows floating point to
|
|
* be calculated here in the kernel by shifting
|
|
* up to integer the floating point significant
|
|
* amount.
|
|
* The oc->cfg->scale.fval must be a 10 base
|
|
* value, e.g. 0.1, 0.01, ... 0.000001, etc.
|
|
* The significance is calculated as:
|
|
* s = (NVS_FLOAT_SIGNIFICANCE_* / scale.fval)
|
|
* so that on_change = HW * resolution * s
|
|
* The NVS HAL will then convert the value to
|
|
* float by multiplying the data with scale.
|
|
*
|
|
* calc = HW * (resolution * NVS_FLOAT_SIGNIFICANCE_) / scale
|
|
*/
|
|
calc_i = calc;
|
|
calc_f = 0;
|
|
if (oc->cfg->resolution.fval) {
|
|
calc_f = calc * oc->cfg->resolution.fval;
|
|
do_div(calc_f, oc->cfg->scale.fval);
|
|
}
|
|
if (oc->cfg->resolution.ival) {
|
|
if (oc->cfg->float_significance)
|
|
calc_i = NVS_FS_NANO;
|
|
else
|
|
calc_i = NVS_FS_MICRO;
|
|
do_div(calc_i, oc->cfg->scale.fval);
|
|
calc_i *= calc * oc->cfg->resolution.ival;
|
|
}
|
|
calc = (s64)(calc_i + calc_f);
|
|
}
|
|
if (oc->calibration != OC_CALIBRATION_DISABLE) {
|
|
if (oc->calibration == OC_CALIBRATION_DONE)
|
|
/* get calibrated value */
|
|
nvs_on_change_interpolate(oc->cfg->uncal_lo,
|
|
calc,
|
|
oc->cfg->uncal_hi,
|
|
oc->cfg->cal_lo,
|
|
&oc->on_change,
|
|
oc->cfg->cal_hi);
|
|
else /* (oc->calibration == OC_CALIBRATION_ENABLE) */
|
|
/* when in calibration mode just return calc */
|
|
oc->on_change = (s32)calc;
|
|
}
|
|
*on_change = calc;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* nvs_on_change_read - called after HW is read and written to oc.
|
|
* @oc: the common structure between driver and common module.
|
|
*
|
|
* This will handle the conversion of HW to on-change value,
|
|
* reporting, calculation of thresholds and poll time.
|
|
*
|
|
* Returns: -1 = Error and/or polling is required for next
|
|
* sample regardless of being interrupt driven.
|
|
* 0 = Do nothing. Value has not changed for reporting
|
|
* and same threshold values if interrupt driven.
|
|
* If not interrupt driven use poll_period_ms.
|
|
* 1 = New HW thresholds are needed.
|
|
* If not interrupt driven use poll_period_ms.
|
|
*/
|
|
int nvs_on_change_read(struct nvs_on_change *oc)
|
|
{
|
|
s32 thresh;
|
|
s64 on_change;
|
|
s64 timestamp_diff;
|
|
s64 period;
|
|
bool report_period_min = true;
|
|
unsigned int poll_period = 0;
|
|
int ret;
|
|
|
|
if (oc->calibration == OC_CALIBRATION_ENABLE)
|
|
/* always report without report_period_min */
|
|
oc->report_n = oc->cfg->report_n;
|
|
if (oc->report_n < oc->cfg->report_n) { /* always report 1st sample */
|
|
/* calculate elapsed time for allowed report rate */
|
|
timestamp_diff = oc->timestamp - oc->timestamp_report;
|
|
period = (s64)oc->period_us * 1000;
|
|
if (timestamp_diff < period) {
|
|
/* data changes are happening faster than allowed to
|
|
* report so we poll for the next data at an allowed
|
|
* rate with interrupts disabled.
|
|
*/
|
|
period -= timestamp_diff;
|
|
do_div(period, 1000); /* ns => us */
|
|
poll_period = period;
|
|
report_period_min = false;
|
|
}
|
|
}
|
|
if (oc->threshold == OC_THRESHOLDS_DISABLE) {
|
|
nvs_on_change_calc(oc, &on_change);
|
|
if (on_change != oc->on_change)
|
|
oc->report_n = oc->cfg->report_n;
|
|
ret = nvs_on_change_push(oc, on_change, poll_period,
|
|
report_period_min);
|
|
return ret;
|
|
}
|
|
|
|
if (oc->threshold == OC_THRESHOLDS_ABSOLUTE) {
|
|
if (oc->thresh_valid_lo && (oc->hw <= oc->hw_thresh_lo)) {
|
|
on_change = 0; /* assume binary_report_en */
|
|
oc->report_n = oc->cfg->report_n;
|
|
/* disable lower threshold */
|
|
oc->thresh_valid_lo = false;
|
|
oc->hw_limit_lo = true;
|
|
/* enable upper threshold */
|
|
oc->thresh_valid_hi = true;
|
|
oc->hw_limit_hi = false;
|
|
} else if (oc->thresh_valid_hi && (oc->hw >=
|
|
oc->hw_thresh_hi)) {
|
|
on_change = 1; /* assume binary_report_en */
|
|
oc->report_n = oc->cfg->report_n;
|
|
/* enable lower threshold */
|
|
oc->thresh_valid_lo = true;
|
|
oc->hw_limit_lo = false;
|
|
/* disable upper threshold */
|
|
oc->thresh_valid_hi = false;
|
|
oc->hw_limit_hi = true;
|
|
}
|
|
if (oc->binary_report_en) {
|
|
if (oc->reverse_range_en)
|
|
/* reverse the value in the range */
|
|
on_change = !on_change;
|
|
if (oc->calibration == OC_CALIBRATION_ENABLE)
|
|
on_change = oc->hw;
|
|
} else if (oc->report_n) {
|
|
nvs_on_change_calc(oc, &on_change);
|
|
}
|
|
ret = nvs_on_change_push(oc, on_change, poll_period,
|
|
report_period_min);
|
|
return ret;
|
|
}
|
|
|
|
/* (oc->threshold == OC_THRESHOLDS_RELATIVE) */
|
|
ret = RET_NO_CHANGE;
|
|
/* thresholds */
|
|
if (oc->thresh_valid_lo && (oc->hw <= oc->hw_thresh_lo))
|
|
oc->report_n = oc->cfg->report_n;
|
|
else if (oc->thresh_valid_hi && (oc->hw > oc->hw_thresh_hi))
|
|
oc->report_n = oc->cfg->report_n;
|
|
/* reporting */
|
|
if (oc->report_n && report_period_min) {
|
|
oc->report_n--;
|
|
oc->timestamp_report = oc->timestamp;
|
|
nvs_on_change_calc(oc, &on_change);
|
|
oc->on_change = on_change;
|
|
/* report on_change */
|
|
oc->handler(oc->nvs_st, &oc->on_change, oc->timestamp_report);
|
|
if (!oc->report_n) {
|
|
/* calculate low threshold */
|
|
thresh = oc->hw - oc->cfg->thresh_lo;
|
|
if (thresh < oc->hw_min) {
|
|
/* low threshold is disabled */
|
|
oc->hw_thresh_lo = oc->hw_min;
|
|
oc->thresh_valid_lo = false;
|
|
oc->hw_limit_lo = true;
|
|
} else {
|
|
oc->hw_thresh_lo = thresh;
|
|
oc->thresh_valid_lo = true;
|
|
oc->hw_limit_lo = false;
|
|
}
|
|
/* calculate high threshold */
|
|
thresh = oc->hw + oc->cfg->thresh_hi;
|
|
if (thresh > oc->hw_max) {
|
|
/* high threshold is disabled */
|
|
oc->hw_thresh_hi = oc->hw_max;
|
|
oc->thresh_valid_hi = false;
|
|
oc->hw_limit_hi = true;
|
|
} else {
|
|
oc->hw_thresh_hi = thresh;
|
|
oc->thresh_valid_hi = true;
|
|
oc->hw_limit_hi = false;
|
|
}
|
|
ret = RET_HW_UPDATE;
|
|
}
|
|
}
|
|
ret = nvs_on_change_poll_period(oc, ret, poll_period,
|
|
report_period_min);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_read);
|
|
|
|
/**
|
|
* nvs_on_change_enable - called when the on_change sensor is enabled.
|
|
* @oc: the common structure between driver and common module.
|
|
*
|
|
* This inititializes the oc NVS variables.
|
|
*
|
|
* Returns 0 on success or a negative error code.
|
|
*/
|
|
int nvs_on_change_enable(struct nvs_on_change *oc)
|
|
{
|
|
int val;
|
|
int ret = 0;
|
|
|
|
if (!oc->cfg->report_n)
|
|
oc->cfg->report_n = 1;
|
|
oc->report_n = oc->cfg->report_n;
|
|
oc->timestamp_report = 0;
|
|
oc->on_change = 1;
|
|
/* determine calibration mode */
|
|
if ((oc->cfg->scale.ival == 1) && (oc->cfg->scale.fval == 0)) {
|
|
oc->calibration = OC_CALIBRATION_ENABLE;
|
|
} else {
|
|
val = oc->cfg->uncal_lo;
|
|
val |= oc->cfg->uncal_hi;
|
|
val |= oc->cfg->cal_lo;
|
|
val |= oc->cfg->cal_hi;
|
|
if (val)
|
|
oc->calibration = OC_CALIBRATION_DONE;
|
|
else
|
|
oc->calibration = OC_CALIBRATION_DISABLE;
|
|
}
|
|
/* determine binary_report_en */
|
|
if ((oc->cfg->resolution.ival == 1) &&
|
|
(oc->cfg->resolution.fval == 0) &&
|
|
(oc->cfg->max_range.ival == 1) &&
|
|
(oc->cfg->max_range.fval == 0)) {
|
|
oc->binary_report_en = true;
|
|
oc->threshold = OC_THRESHOLDS_ABSOLUTE;
|
|
oc->hw_thresh_hi = oc->cfg->thresh_hi;
|
|
oc->hw_thresh_lo = oc->cfg->thresh_lo;
|
|
oc->thresh_valid_lo = true;
|
|
oc->thresh_valid_hi = true;
|
|
} else {
|
|
oc->binary_report_en = false;
|
|
/* determine thresholds */
|
|
oc->hw_limit_lo = false;
|
|
oc->hw_limit_hi = false;
|
|
val = oc->cfg->thresh_lo;
|
|
val |= oc->cfg->thresh_hi;
|
|
if (val) {
|
|
if (oc->threshold == OC_THRESHOLDS_ABSOLUTE) {
|
|
oc->hw_thresh_hi = oc->cfg->thresh_hi;
|
|
oc->hw_thresh_lo = oc->cfg->thresh_lo;
|
|
} else { /* oc->threshold == OC_THRESHOLDS_RELATIVE */
|
|
oc->threshold = OC_THRESHOLDS_RELATIVE;
|
|
oc->hw_thresh_hi = oc->hw_min;
|
|
oc->hw_thresh_lo = oc->hw_max;
|
|
}
|
|
oc->thresh_valid_lo = true;
|
|
oc->thresh_valid_hi = true;
|
|
} else { /* oc->threshold == OC_THRESHOLDS_DISABLE */
|
|
oc->threshold = OC_THRESHOLDS_DISABLE;
|
|
oc->thresh_valid_lo = false;
|
|
oc->thresh_valid_hi = false;
|
|
}
|
|
}
|
|
if (oc->period_us)
|
|
oc->poll_period_ms = oc->period_us * 1000;
|
|
else
|
|
oc->poll_period_ms = oc->cfg->delay_us_min * 1000;
|
|
if (oc->hw_max <= oc->hw_min)
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_enable);
|
|
|
|
/**
|
|
* nvs_on_change_threshold_lo - runtime mechanism to modify low
|
|
* threshold value.
|
|
* @oc: the common structure between driver and common module.
|
|
*
|
|
* NOTE: If in calibration mode then calibrated/uncalibrated low
|
|
* values are modified instead.
|
|
*/
|
|
int nvs_on_change_threshold_lo(struct nvs_on_change *oc, int lo)
|
|
{
|
|
s32 lo_max;
|
|
|
|
if (oc->calibration == OC_CALIBRATION_ENABLE) {
|
|
oc->cfg->uncal_lo = oc->on_change;
|
|
oc->cfg->cal_lo = lo;
|
|
} else if (oc->threshold == OC_THRESHOLDS_ABSOLUTE) {
|
|
if (lo > oc->hw_max)
|
|
lo = oc->hw_max;
|
|
else if (lo < oc->hw_min)
|
|
lo = oc->hw_min;
|
|
oc->cfg->thresh_lo = lo;
|
|
} else if (oc->threshold == OC_THRESHOLDS_RELATIVE) {
|
|
lo_max = oc->hw_max - oc->hw_min;
|
|
if (lo > lo_max)
|
|
lo = lo_max;
|
|
else if (lo < 0)
|
|
lo = 0;
|
|
oc->cfg->thresh_lo = lo;
|
|
} else {
|
|
oc->cfg->thresh_lo = lo;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_threshold_lo);
|
|
|
|
/**
|
|
* nvs_on_change_threshold_hi - runtime mechanism to modify high
|
|
* threshold value.
|
|
* @oc: the common structure between driver and common module.
|
|
*
|
|
* NOTE: If in calibration mode then calibrated/uncalibrated
|
|
* high values are modified instead.
|
|
*/
|
|
int nvs_on_change_threshold_hi(struct nvs_on_change *oc, int hi)
|
|
{
|
|
s32 hi_max;
|
|
|
|
if (oc->calibration == OC_CALIBRATION_ENABLE) {
|
|
oc->cfg->uncal_hi = oc->on_change;
|
|
oc->cfg->cal_hi = hi;
|
|
} else if (oc->threshold == OC_THRESHOLDS_ABSOLUTE) {
|
|
if (hi > oc->hw_max)
|
|
hi = oc->hw_max;
|
|
else if (hi < oc->hw_min)
|
|
hi = oc->hw_min;
|
|
oc->cfg->thresh_hi = hi;
|
|
} else if (oc->threshold == OC_THRESHOLDS_RELATIVE) {
|
|
hi_max = oc->hw_max - oc->hw_min;
|
|
if (hi > hi_max)
|
|
hi = hi_max;
|
|
else if (hi < 0)
|
|
hi = 0;
|
|
oc->cfg->thresh_hi = hi;
|
|
} else {
|
|
oc->cfg->thresh_hi = hi;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(nvs_on_change_threshold_hi);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("NVidia Sensor on-change module");
|
|
MODULE_AUTHOR("NVIDIA Corporation");
|
|
|