646 lines
24 KiB
C
646 lines
24 KiB
C
|
/* Copyright (c) 2014-2017, 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 proximity module allows, along with the NVS IIO common
|
||
|
* module, a proximity driver to offload the code interacting with IIO and
|
||
|
* proximity reporting, and just have code that interacts with the HW.
|
||
|
* The commonality between this module and the NVS ALS driver is the
|
||
|
* nvs_proximity structure. It is expected that the NVS proximity driver will:
|
||
|
* - call nvs_proximity_enable when the device is enabled for initialization.
|
||
|
* - read the HW and place the value in nvs_proximity.hw
|
||
|
* - call nvs_proximity_read
|
||
|
* - depending on the nvs_proximity_read return value:
|
||
|
* - -1 = poll HW using nvs_proximity.poll_delay_ms delay.
|
||
|
* - 0 = if interrupt driven, do nothing or resume regular polling
|
||
|
* - 1 = set new thresholds using the nvs_proximity.hw_thresh_lo/hi
|
||
|
* Reporting the distance is handled within this module.
|
||
|
* See nvs_proximity.h for nvs_proximity structure details.
|
||
|
*/
|
||
|
/* NVS proximity 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 so that a 1 is reported for
|
||
|
* "far away" and 0 for "near". The low threshold is typically set for maximum
|
||
|
* range allowing the minimal LED drive power to determine the actual range.
|
||
|
* If proximity binary output is disabled, the driver will then require the
|
||
|
* interpolation calibration for reporting actual distances.
|
||
|
*/
|
||
|
/* The NVS HAL will use the IIO 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.
|
||
|
*/
|
||
|
/* Because the proximity HW can use dynamic resolution depending on the
|
||
|
* distance range, configuration threshold values are HW based. In other
|
||
|
* words, the threshold will automatically scale based on the resolution.
|
||
|
*/
|
||
|
/* If the NVS proximity driver is not configured for binary output, then
|
||
|
* there are two calibration mechanisms that can be used:
|
||
|
* Method 1 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. So instead.a mechanism allows floating point to be calculated
|
||
|
* here in the kernel by shifting up to integer the floating point significant
|
||
|
* amount. This allows real-time resolution changes without the NVS HAL having
|
||
|
* to synchronize to the actual resolution for each datum. 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.
|
||
|
* Method 1:
|
||
|
* 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.
|
||
|
* 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 proximity sensor has the following device tree parameters for this:
|
||
|
* proximity_uncalibrated_lo
|
||
|
* proximity_calibrated_lo
|
||
|
* proximity_uncalibrated_hi
|
||
|
* proximity_calibrated_hi
|
||
|
*
|
||
|
* An NVS proximity driver may support a simplified version of method 1 that
|
||
|
* can be used in realtime:
|
||
|
* At step 8, write the calibrated value to the in_proximity_threshold_low
|
||
|
* attribute. When in calibration mode this value will be written to the
|
||
|
* proximity_calibrated_lo and the current proximity written to
|
||
|
* proximity_uncalibrated_lo internal to the driver.
|
||
|
* If this is after step 9, then use the in_proximity_threshold_high
|
||
|
* attribute.
|
||
|
* Note that the calibrated value must be the value before the scale and offset
|
||
|
* is applied. For example, if the calibrated proximity reading is 123.4 cm,
|
||
|
* and the in_proximity_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:
|
||
|
* proximity_scale_ival = the integer value of the scale.
|
||
|
* proximity_scale_fval = the floating value of the scale.
|
||
|
* proximity_offset_ival = the integer value of the offset.
|
||
|
* proximity_offset_fval = the floating value of the offset.
|
||
|
* The values are in the NVS_FLOAT_SIGNIFICANCE_ format (see nvs.h).
|
||
|
*/
|
||
|
/* If the NVS proximity driver is configured for binary output, then
|
||
|
* interpolation is not used and the thresholds are used to trigger either the
|
||
|
* 0 or 1 output. To calibrate 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 proximity data.
|
||
|
* 5. Move an object (your hand) through the proximity 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
|
||
|
* proximity 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 proximity sensor has the following device tree parameters for this:
|
||
|
* proximity_threshold_lo
|
||
|
* proximity_threshold_hi
|
||
|
*/
|
||
|
/* If the NVS proximity driver is not configured for binary output, then the
|
||
|
* thresholds are used for hysterysis. The threshold settings are HW based and
|
||
|
* allow a window around the last reported HW value. For example, if the low
|
||
|
* threshold is set to 10 and the high threshold set to 20, if the proximity HW
|
||
|
* value is 100, the proximity won't be reported again until the proximity HW
|
||
|
* value is either < 90 or > than 120.
|
||
|
* The low/high threshold values are typically the same, but they can be
|
||
|
* configured so that proximity 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,
|
||
|
* proximity_report_count in DT (see nvs.h). This allows additional
|
||
|
* reporting of proximity a set amount of times while still within the
|
||
|
* threshold window.
|
||
|
*/
|
||
|
/* If the NVS proximity 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 HW value.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/nvs_proximity.h>
|
||
|
|
||
|
#define NVS_PROXIMITY_VERSION (102)
|
||
|
#define NVS_FS_NANO NVS_FLOAT_SIGNIFICANCE_NANO
|
||
|
#define NVS_FS_MICRO NVS_FLOAT_SIGNIFICANCE_MICRO
|
||
|
|
||
|
|
||
|
ssize_t nvs_proximity_dbg(struct nvs_proximity *np, char *buf)
|
||
|
{
|
||
|
ssize_t t;
|
||
|
|
||
|
t = snprintf(buf, PAGE_SIZE, "%s v.%u:\n",
|
||
|
__func__, NVS_PROXIMITY_VERSION);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "timestamp=%lld\n",
|
||
|
np->timestamp);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "timestamp_report=%lld\n",
|
||
|
np->timestamp_report);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "proximity=%u\n", np->proximity);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw=%u\n", np->hw);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw_mask=%x\n", np->hw_mask);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_lo=%u\n",
|
||
|
np->hw_thresh_lo);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_hi=%u\n",
|
||
|
np->hw_thresh_hi);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_lo=%x\n",
|
||
|
np->hw_limit_lo);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_hi=%x\n",
|
||
|
np->hw_limit_hi);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_lo=%x\n",
|
||
|
np->thresh_valid_lo);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_hi=%x\n",
|
||
|
np->thresh_valid_hi);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "thresholds_valid=%x\n",
|
||
|
np->thresholds_valid);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "calibration_en=%x\n",
|
||
|
np->calibration_en);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "dynamic_resolution_dis=%x\n",
|
||
|
np->dynamic_resolution_dis);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t,
|
||
|
"proximity_reverse_range_dis=%x\n",
|
||
|
np->proximity_reverse_range_dis);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "proximity_binary_en=%x\n",
|
||
|
np->proximity_binary_en);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "proximity_binary_hw=%x\n",
|
||
|
np->proximity_binary_hw);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "poll_delay_ms=%u\n",
|
||
|
np->poll_delay_ms);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "delay_us=%u\n", np->delay_us);
|
||
|
t += snprintf(buf + t, PAGE_SIZE - t, "report=%u\n", np->report);
|
||
|
return t;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_dbg);
|
||
|
|
||
|
static void nvs_proximity_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) {
|
||
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 3, 0)
|
||
|
dividend = abs64(dividend);
|
||
|
#else
|
||
|
dividend = abs(dividend);
|
||
|
#endif
|
||
|
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_proximity_poll_delay(struct nvs_proximity *np, int ret,
|
||
|
unsigned int poll_delay,
|
||
|
bool report_delay_min)
|
||
|
{
|
||
|
if (report_delay_min)
|
||
|
poll_delay = np->delay_us;
|
||
|
if ((poll_delay < np->cfg->delay_us_min) || np->calibration_en)
|
||
|
poll_delay = np->cfg->delay_us_min;
|
||
|
np->poll_delay_ms = poll_delay / 1000;
|
||
|
if (np->report || np->calibration_en)
|
||
|
ret = RET_POLL_NEXT; /* poll for next sample */
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* nvs_proximity_read - called after HW is read and written to
|
||
|
* np.
|
||
|
* @np: the common structure between driver and common module.
|
||
|
*
|
||
|
* This will handle the conversion of HW to distance 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_delay_ms.
|
||
|
* 1 = New HW thresholds are needed.
|
||
|
* If not interrupt driven use poll_delay_ms.
|
||
|
*/
|
||
|
int nvs_proximity_read(struct nvs_proximity *np)
|
||
|
{
|
||
|
u64 hw_distance;
|
||
|
u64 calc_i;
|
||
|
u64 calc_f;
|
||
|
s64 calc;
|
||
|
s64 timestamp_diff;
|
||
|
s64 delay;
|
||
|
bool report_delay_min = true;
|
||
|
unsigned int poll_delay = 0;
|
||
|
unsigned int thresh_lo;
|
||
|
unsigned int thresh_hi;
|
||
|
int ret;
|
||
|
|
||
|
if (np->calibration_en)
|
||
|
/* always report without report_delay_min */
|
||
|
np->report = np->cfg->report_n;
|
||
|
if (np->report < np->cfg->report_n) { /* always report first sample */
|
||
|
/* calculate elapsed time for allowed report rate */
|
||
|
timestamp_diff = np->timestamp - np->timestamp_report;
|
||
|
delay = (s64)np->delay_us * 1000;
|
||
|
if (timestamp_diff < delay) {
|
||
|
/* data changes are happening faster than allowed to
|
||
|
* report so we poll for the next data at an allowed
|
||
|
* rate with interrupts disabled.
|
||
|
*/
|
||
|
delay -= timestamp_diff;
|
||
|
do_div(delay, 1000); /* ns => us */
|
||
|
poll_delay = delay;
|
||
|
report_delay_min = false;
|
||
|
}
|
||
|
}
|
||
|
if (np->proximity_binary_hw) {
|
||
|
/* this device has proximity binary HW (HW reads 0 or 1)
|
||
|
* so just report value if changed
|
||
|
*/
|
||
|
ret = RET_NO_CHANGE;
|
||
|
if (np->hw != np->proximity) {
|
||
|
np->proximity = np->hw;
|
||
|
np->report = np->cfg->report_n;
|
||
|
}
|
||
|
if (np->calibration_en)
|
||
|
np->report = np->cfg->report_n;
|
||
|
if (np->report && report_delay_min) {
|
||
|
np->report--;
|
||
|
np->timestamp_report = np->timestamp;
|
||
|
np->handler(np->nvs_st, &np->proximity,
|
||
|
np->timestamp_report);
|
||
|
ret = RET_HW_UPDATE;
|
||
|
}
|
||
|
return nvs_proximity_poll_delay(np, ret, poll_delay,
|
||
|
report_delay_min);
|
||
|
}
|
||
|
/* threshold flags */
|
||
|
thresh_lo = np->cfg->thresh_lo;
|
||
|
thresh_hi = np->cfg->thresh_hi;
|
||
|
if (thresh_lo < np->hw_mask) {
|
||
|
np->thresh_valid_lo = true;
|
||
|
} else {
|
||
|
np->thresh_valid_lo = false;
|
||
|
thresh_lo = 0;
|
||
|
}
|
||
|
if (thresh_hi < np->hw_mask) {
|
||
|
np->thresh_valid_hi = true;
|
||
|
} else {
|
||
|
np->thresh_valid_hi = false;
|
||
|
thresh_hi = 0;
|
||
|
}
|
||
|
if (np->thresh_valid_lo && np->thresh_valid_hi)
|
||
|
np->thresholds_valid = true;
|
||
|
else
|
||
|
np->thresholds_valid = false;
|
||
|
/* limit flags */
|
||
|
if ((np->hw < thresh_lo) || (np->hw == 0))
|
||
|
np->hw_limit_lo = true;
|
||
|
else
|
||
|
np->hw_limit_lo = false;
|
||
|
if (np->proximity_binary_en) {
|
||
|
if (np->hw > thresh_hi)
|
||
|
np->hw_limit_hi = true;
|
||
|
else
|
||
|
np->hw_limit_hi = false;
|
||
|
} else {
|
||
|
if ((np->hw == np->hw_mask) || (np->hw >
|
||
|
(np->hw_mask - thresh_hi)))
|
||
|
np->hw_limit_hi = true;
|
||
|
else
|
||
|
np->hw_limit_hi = false;
|
||
|
}
|
||
|
ret = RET_NO_CHANGE;
|
||
|
if (np->proximity_binary_en) {
|
||
|
/* proximity has binary threshold */
|
||
|
if (!np->thresholds_valid) {
|
||
|
/* Invalid thresholds is an NVS feature that forces
|
||
|
* polling. However, with this binary mechanism,
|
||
|
* thresholds are required. So although the feature
|
||
|
* is somewhat crippled, we make it work by setting
|
||
|
* the trigger in the middle of the HW range.
|
||
|
*/
|
||
|
thresh_lo = np->hw_mask / 2;
|
||
|
thresh_hi = thresh_lo;
|
||
|
np->report = np->cfg->report_n;
|
||
|
}
|
||
|
if (np->hw < np->hw_thresh_lo) {
|
||
|
np->proximity = 1;
|
||
|
np->report = np->cfg->report_n;
|
||
|
/* disable lower threshold */
|
||
|
np->hw_thresh_lo = 0;
|
||
|
/* enable upper threshold */
|
||
|
np->hw_thresh_hi = thresh_hi;
|
||
|
} else if (np->hw > np->hw_thresh_hi) {
|
||
|
np->proximity = 0;
|
||
|
np->report = np->cfg->report_n;
|
||
|
/* disable upper threshold */
|
||
|
np->hw_thresh_hi = np->hw_mask;
|
||
|
/* enable lower threshold */
|
||
|
np->hw_thresh_lo = thresh_lo;
|
||
|
}
|
||
|
if (np->calibration_en)
|
||
|
np->proximity = np->hw;
|
||
|
if (np->report && report_delay_min) {
|
||
|
np->report--;
|
||
|
np->timestamp_report = np->timestamp;
|
||
|
np->handler(np->nvs_st, &np->proximity,
|
||
|
np->timestamp_report);
|
||
|
ret = RET_HW_UPDATE;
|
||
|
}
|
||
|
} else {
|
||
|
/* reporting and thresholds */
|
||
|
if (np->thresholds_valid) {
|
||
|
if (np->hw < np->hw_thresh_lo)
|
||
|
np->report = np->cfg->report_n;
|
||
|
else if (np->hw > np->hw_thresh_hi)
|
||
|
np->report = np->cfg->report_n;
|
||
|
} else {
|
||
|
/* report everything if no thresholds */
|
||
|
np->report = np->cfg->report_n;
|
||
|
}
|
||
|
if (np->report && report_delay_min) {
|
||
|
np->report--;
|
||
|
np->timestamp_report = np->timestamp;
|
||
|
if (np->proximity_reverse_range_dis)
|
||
|
hw_distance = np->hw;
|
||
|
else
|
||
|
/* reverse the value in the range */
|
||
|
hw_distance = np->hw_mask - np->hw;
|
||
|
/* distance = HW * (resolution *
|
||
|
* NVS_FLOAT_SIGNIFICANCE_) / scale
|
||
|
*/
|
||
|
calc_i = hw_distance;
|
||
|
calc_f = 0;
|
||
|
if (np->cfg->scale.fval &&
|
||
|
!np->dynamic_resolution_dis) {
|
||
|
/* The mechanism below allows floating point to
|
||
|
* be calculated here in the kernel by shifting
|
||
|
* up to integer the floating point significant
|
||
|
* amount.
|
||
|
* The nl->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 proximity = HW * resolution * s
|
||
|
* The NVS HAL will then convert the value to
|
||
|
* float by multiplying the data with scale.
|
||
|
*/
|
||
|
if (np->cfg->resolution.fval) {
|
||
|
calc_f = hw_distance *
|
||
|
np->cfg->resolution.fval;
|
||
|
do_div(calc_f, np->cfg->scale.fval);
|
||
|
}
|
||
|
if (np->cfg->resolution.ival) {
|
||
|
if (np->cfg->float_significance)
|
||
|
calc_i = NVS_FS_NANO;
|
||
|
else
|
||
|
calc_i = NVS_FS_MICRO;
|
||
|
do_div(calc_i, np->cfg->scale.fval);
|
||
|
calc_i *= hw_distance *
|
||
|
np->cfg->resolution.ival;
|
||
|
}
|
||
|
}
|
||
|
calc = (s64)(calc_i + calc_f);
|
||
|
if (np->calibration_en)
|
||
|
/* when in calibration mode just return calc */
|
||
|
np->proximity = (u32)calc;
|
||
|
else
|
||
|
/* get calibrated value */
|
||
|
nvs_proximity_interpolate(np->cfg->uncal_lo,
|
||
|
calc,
|
||
|
np->cfg->uncal_hi,
|
||
|
np->cfg->cal_lo,
|
||
|
&np->proximity,
|
||
|
np->cfg->cal_hi);
|
||
|
/* report proximity */
|
||
|
np->handler(np->nvs_st, &np->proximity,
|
||
|
np->timestamp_report);
|
||
|
if ((np->thresholds_valid) && !np->report) {
|
||
|
/* calculate low threshold */
|
||
|
calc = (s64)np->hw;
|
||
|
calc -= thresh_lo;
|
||
|
if (calc < 0)
|
||
|
/* low threshold is disabled */
|
||
|
np->hw_thresh_lo = 0;
|
||
|
else
|
||
|
np->hw_thresh_lo = calc;
|
||
|
/* calculate high threshold */
|
||
|
calc = np->hw + thresh_hi;
|
||
|
if (calc > np->hw_mask)
|
||
|
/* high threshold is disabled */
|
||
|
np->hw_thresh_hi = np->hw_mask;
|
||
|
else
|
||
|
np->hw_thresh_hi = calc;
|
||
|
ret = RET_HW_UPDATE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nvs_proximity_poll_delay(np, ret, poll_delay, report_delay_min);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_read);
|
||
|
|
||
|
/**
|
||
|
* nvs_proximity_enable - called when the proximity sensor is
|
||
|
* enabled.
|
||
|
* @np: the common structure between driver and common module.
|
||
|
*
|
||
|
* This inititializes the np NVS variables.
|
||
|
*
|
||
|
* Returns 0 on success or a negative error code.
|
||
|
*/
|
||
|
int nvs_proximity_enable(struct nvs_proximity *np)
|
||
|
{
|
||
|
if (!np->cfg->report_n)
|
||
|
np->cfg->report_n = 1;
|
||
|
np->report = np->cfg->report_n;
|
||
|
np->timestamp_report = 0;
|
||
|
np->hw_thresh_hi = 0;
|
||
|
np->hw_thresh_lo = -1;
|
||
|
np->proximity = 1;
|
||
|
if (np->cfg->resolution.ival == 1 && !np->cfg->resolution.fval &&
|
||
|
np->cfg->max_range.ival == 1 && !np->cfg->max_range.fval)
|
||
|
np->proximity_binary_en = true;
|
||
|
else
|
||
|
np->proximity_binary_en = false;
|
||
|
if (np->cfg->scale.ival == 1 && !np->cfg->scale.fval)
|
||
|
np->calibration_en = true;
|
||
|
else
|
||
|
np->calibration_en = false;
|
||
|
if (np->delay_us)
|
||
|
np->poll_delay_ms = np->delay_us * 1000;
|
||
|
else
|
||
|
np->poll_delay_ms = np->cfg->delay_us_min * 1000;
|
||
|
if (np->hw_mask == 1)
|
||
|
np->proximity_binary_hw = true;
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_enable);
|
||
|
|
||
|
/**
|
||
|
* nvs_proximity_of_dt - called during system boot for
|
||
|
* configuration from device tree.
|
||
|
* @np: the common structure between driver and common module.
|
||
|
* @dn: device node pointer.
|
||
|
* @dev_name: device name string. Typically a string to
|
||
|
* "proximity" or NULL.
|
||
|
*
|
||
|
* Returns 0 on success or a negative error code.
|
||
|
*
|
||
|
* Driver must initialize variables if no success.
|
||
|
*/
|
||
|
int nvs_proximity_of_dt(struct nvs_proximity *np, const struct device_node *dn,
|
||
|
const char *dev_name)
|
||
|
{
|
||
|
s32 binary_hw = -1;
|
||
|
char str[256];
|
||
|
int ret;
|
||
|
|
||
|
if (np->cfg)
|
||
|
np->cfg->flags |= SENSOR_FLAG_ON_CHANGE_MODE;
|
||
|
if (dn == NULL)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (dev_name == NULL)
|
||
|
dev_name = NVS_PROXIMITY_STRING;
|
||
|
ret = snprintf(str, sizeof(str), "%s_binary_hw", dev_name);
|
||
|
if (ret > 0)
|
||
|
of_property_read_s32(dn, str, &binary_hw);
|
||
|
if (binary_hw > 0)
|
||
|
np->proximity_binary_hw = true;
|
||
|
else if (!binary_hw)
|
||
|
np->proximity_binary_hw = false;
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_of_dt);
|
||
|
|
||
|
/**
|
||
|
* nvs_proximity_threshold_calibrate_lo - runtime mechanism to
|
||
|
* modify calibrated/uncalibrated low value.
|
||
|
* @np: the common structure between driver and common module.
|
||
|
*
|
||
|
* NOTE: If not in calibration mode then thresholds are modified
|
||
|
* instead.
|
||
|
*/
|
||
|
void nvs_proximity_threshold_calibrate_lo(struct nvs_proximity *np, int lo)
|
||
|
{
|
||
|
|
||
|
if (np->calibration_en) {
|
||
|
np->cfg->uncal_lo = np->proximity;
|
||
|
np->cfg->cal_lo = lo;
|
||
|
} else {
|
||
|
np->cfg->thresh_lo = lo;
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_threshold_calibrate_lo);
|
||
|
|
||
|
/**
|
||
|
* nvs_proximity_threshold_calibrate_hi - runtime mechanism to
|
||
|
* modify calibrated/uncalibrated high value.
|
||
|
* @nl: the common structure between driver and common module.
|
||
|
*
|
||
|
* NOTE: If not in calibration mode then thresholds are modified
|
||
|
* instead.
|
||
|
*/
|
||
|
void nvs_proximity_threshold_calibrate_hi(struct nvs_proximity *np, int hi)
|
||
|
{
|
||
|
|
||
|
if (np->calibration_en) {
|
||
|
np->cfg->uncal_hi = np->proximity;
|
||
|
np->cfg->cal_hi = hi;
|
||
|
} else {
|
||
|
np->cfg->thresh_hi = hi;
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(nvs_proximity_threshold_calibrate_hi);
|
||
|
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_DESCRIPTION("NVidia Sensor proximity module");
|
||
|
MODULE_AUTHOR("NVIDIA Corporation");
|
||
|
|