/* 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 ALS module allows, along with the NVS IIO common module, an * ALS driver to offload the code interacting with IIO and ALS reporting, and * just have code that interacts with the HW. * The commonality between this module and the NVS ALS driver is the nvs_light * structure. It is expected that the NVS ALS driver will: * - call nvs_light_enable when the device is enabled to initialize variables. * - read the HW and place the value in nvs_light.hw * - call nvs_light_read * - depending on the nvs_light_read return value: * - -1 = poll HW using nvs_light.poll_delay_ms delay. * - 0 = if interrupt driven, do nothing or resume regular polling * - 1 = set new thresholds using the nvs_light.hw_thresh_lo/hi * Reporting the lux is handled within this module. * See nvs_light.h for nvs_light structure details. */ /* 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. */ /* The configuration threshold values are HW value based. In other words, to * obtain the upper and lower HW thresholds, the configuration threshold is * simply added or subtracted from the HW data read, respectively. * A little history about this: this code originally expected the configuration * threshold values to be in lux. It then converted the threshold lux value to * a HW value by reversing the calibrated value to uncalibrated and then the * scaling of the resolution. The idea was to make configuration easy by just * setting how much lux needed to change before reporting. However, there were * two issues with this method: * 1) that's a lot of overhead for each sample, and 2) the lux range isn't * exactly linear. Lux values in a dark room will probably want to be reported * every +/- 100 or 10 if not less. This is opposed to a bright room or even * outside on a sunny day where lux value changes can be reported every * +/- 1000 or even 10000. Since many ALS's have dynamic resolution, changing * the range depending on the lux reading, it makes sense to use HW threshold * values that will automatically scale with the HW resolution used. */ /* NVS light drivers have two calibration mechanisms. 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. (Write 0 = buffer/enable sysfs attribute) * 3. Write 1 to the scale sysfs attribute (e.g. in_illuminance_scale). * 4. Enable device. (Write 1 = buffer/enable sysfs attribute) * 5. The NVS HAL will announce in the 'logcat -v time | grep -i "sensors"' * 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. * (Generate light source < 100 lux.) * 8. Note the values displayed in the log. Separately measure the actual * value with a high accuracy light meter. Make sure the light meter * is calibrated to the same color temperature as your light source. * 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). * Device Tree nodes related to light calibration: * light_uncalibrated_lo <-- Value found in logcat. * light_calibrated_lo <-- Value read from light meter. * 9. Put the device into a state where the data read is a high value. * (Generate light source > 3000 lux.) * 10. Repeat step 8. * Device Tree nodes related to light calibration: * light_uncalibrated_hi <-- Value found in logcat. * light_calibrated_hi <-- Value read from light meter. * 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 light sensor has the following device tree parameters for this: * light_uncalibrated_lo * light_calibrated_lo * light_uncalibrated_hi * light_calibrated_hi * * To test calibration values in realtime. * An NVS ALS driver may support a simplified version of method 1 that can be * used in realtime: * 1. At step 8, while the light source is still at this low value, * write the light meter value to the in_illuminance_threshold_low * attribute. When in calibration mode this value will be written to the * light_calibrated_lo and the current lux written to light_uncalibrated_lo * internal to the driver. * 2. If this is after step 9, while the light source is still at this high * value, write the light meter value to the in_illuminance_threshold_high * attribute. * 3. 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=52 <-- Program this value into DT: light_uncalibrated_lo * uncal_hi=1627 <-- Program this value into DT: light_uncalibrated_hi * cal_lo=8050 <-- Program this value into DT: light_calibrated_lo * cal_hi=308000 <-- Program this value into DT: light_calibrated_hi * thresh_lo=10 * thresh_hi=10 * ... * Note that the calibrated value must be the value before the scale and offset * is applied. For example, if the calibrated lux reading is 123.4 lux, and * the in_illuminance_scale is normally 0.01, then the value entered is 12340 * which will be 123.4 lux when the scale is applied at the HAL layer. * * 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: * light_scale_ival = the integer value of the scale. * light_scale_fval = the floating value of the scale. * light_offset_ival = the integer value of the offset. * light_offset_fval = the floating value of the offset. * The values are in the NVS_FLOAT_SIGNIFICANCE_ format (see nvs.h). */ #include #include #include #include #define NVS_LIGHT_VERSION (106) ssize_t nvs_light_dbg(struct nvs_light *nl, char *buf) { ssize_t t; unsigned int n; unsigned int i; t = snprintf(buf, PAGE_SIZE, "%s v.%u:\n", __func__, NVS_LIGHT_VERSION); t += snprintf(buf + t, PAGE_SIZE - t, "timestamp=%lld\n", nl->timestamp); t += snprintf(buf + t, PAGE_SIZE - t, "timestamp_report=%lld\n", nl->timestamp_report); t += snprintf(buf + t, PAGE_SIZE - t, "lux=%u\n", nl->lux); t += snprintf(buf + t, PAGE_SIZE - t, "lux_max=%u\n", nl->lux_max); t += snprintf(buf + t, PAGE_SIZE - t, "hw=%u\n", nl->hw); t += snprintf(buf + t, PAGE_SIZE - t, "hw_mask=%x\n", nl->hw_mask); t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_lo=%u\n", nl->hw_thresh_lo); t += snprintf(buf + t, PAGE_SIZE - t, "hw_thresh_hi=%u\n", nl->hw_thresh_hi); t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_lo=%x\n", nl->hw_limit_lo); t += snprintf(buf + t, PAGE_SIZE - t, "hw_limit_hi=%x\n", nl->hw_limit_hi); t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_lo=%x\n", nl->thresh_valid_lo); t += snprintf(buf + t, PAGE_SIZE - t, "thresh_valid_hi=%x\n", nl->thresh_valid_hi); t += snprintf(buf + t, PAGE_SIZE - t, "thresholds_valid=%x\n", nl->thresholds_valid); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_change=%x\n", nl->nld_i_change); t += snprintf(buf + t, PAGE_SIZE - t, "calibration_en=%x\n", nl->calibration_en); t += snprintf(buf + t, PAGE_SIZE - t, "poll_delay_ms=%u\n", nl->poll_delay_ms); t += snprintf(buf + t, PAGE_SIZE - t, "delay_us=%u\n", nl->delay_us); t += snprintf(buf + t, PAGE_SIZE - t, "report=%u\n", nl->report); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i=%u\n", nl->nld_i); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_lo=%u\n", nl->nld_i_lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_i_hi=%u\n", nl->nld_i_hi); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl_n=%u\n", nl->nld_tbl_n); if (nl->nld_tbl) { if (nl->nld_tbl_n) { i = 0; n = nl->nld_tbl_n; } else { i = nl->nld_i_lo; n = nl->nld_i_hi + 1; } for (; i < n; i++) { if (nl->nld_thr) { t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].lo=%u\n", i, nl->nld_thr[i].lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].hi=%u\n", i, nl->nld_thr[i].hi); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].i_lo=%u\n", i, nl->nld_thr[i].i_lo); t += snprintf(buf + t, PAGE_SIZE - t, "nld_thr[%u].i_hi=%u\n", i, nl->nld_thr[i].i_hi); } if (nl->cfg->float_significance) { t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].resolution=%d.%09u\n", i, nl->nld_tbl[i].resolution.ival, nl->nld_tbl[i].resolution.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].max_range=%d.%09u\n", i, nl->nld_tbl[i].max_range.ival, nl->nld_tbl[i].max_range.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].milliamp=%d.%09u\n", i, nl->nld_tbl[i].milliamp.ival, nl->nld_tbl[i].milliamp.fval); } else { t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].resolution=%d.%06u\n", i, nl->nld_tbl[i].resolution.ival, nl->nld_tbl[i].resolution.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].max_range=%d.%06u\n", i, nl->nld_tbl[i].max_range.ival, nl->nld_tbl[i].max_range.fval); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].milliamp=%d.%06u\n", i, nl->nld_tbl[i].milliamp.ival, nl->nld_tbl[i].milliamp.fval); } t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].delay_min_ms=%u\n", i, nl->nld_tbl[i].delay_min_ms); t += snprintf(buf + t, PAGE_SIZE - t, "nld_tbl[%d].driver_data=%u\n", i, nl->nld_tbl[i].driver_data); } } return t; } EXPORT_SYMBOL_GPL(nvs_light_dbg); static u32 nvs_light_interpolate(int x1, s64 x2, int x3, int y1, int y3) { s64 dividend; s64 divisor; /* y2 = ((x2 - x1)(y3 - y1)/(x3 - x1)) + y1 */ divisor = (x3 - x1); if (!divisor) return (u32)x2; 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; return (u32)dividend; } static int nvs_light_nld(struct nvs_light *nl, unsigned int nld_i) { nl->nld_i = nld_i; nl->nld_i_change = true; nl->cfg->resolution.ival = nl->nld_tbl[nld_i].resolution.ival; nl->cfg->resolution.fval = nl->nld_tbl[nld_i].resolution.fval; nl->cfg->max_range.ival = nl->nld_tbl[nld_i].max_range.ival; nl->cfg->max_range.fval = nl->nld_tbl[nld_i].max_range.fval; nl->cfg->milliamp.ival = nl->nld_tbl[nld_i].milliamp.ival; nl->cfg->milliamp.fval = nl->nld_tbl[nld_i].milliamp.fval; nl->cfg->delay_us_min = nl->nld_tbl[nld_i].delay_min_ms * 1000; return RET_POLL_NEXT; } /** * nvs_light_read - called after HW is read and placed in nl. * @nl: the common structure between driver and common module. * * This will handle the conversion of HW to lux 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. Lux 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_light_read(struct nvs_light *nl) { 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 thr_lo; unsigned int thr_hi; int ret; if (nl->calibration_en) /* always report without report_delay_min */ nl->report = nl->cfg->report_n; if (nl->report < nl->cfg->report_n) { /* always report first sample */ /* calculate elapsed time for allowed report rate */ timestamp_diff = nl->timestamp - nl->timestamp_report; delay = (s64)nl->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; } } /* threshold flags */ if (nl->nld_thr) { thr_lo = nl->nld_thr[nl->nld_i].lo; thr_hi = nl->nld_thr[nl->nld_i].hi; } else { thr_lo = nl->cfg->thresh_lo; thr_hi = nl->cfg->thresh_hi; } if (thr_lo < nl->hw_mask) { nl->thresh_valid_lo = true; } else { nl->thresh_valid_lo = false; thr_lo = 0; } if (thr_hi < nl->hw_mask) { nl->thresh_valid_hi = true; } else { nl->thresh_valid_hi = false; thr_hi = 0; } if (nl->thresh_valid_lo && nl->thresh_valid_hi) nl->thresholds_valid = true; else nl->thresholds_valid = false; /* limit flags */ if (nl->nld_thr) { /* using absolute values */ if (nl->hw < nl->nld_thr[nl->nld_i].i_lo) nl->hw_limit_lo = true; else nl->hw_limit_lo = false; if (nl->hw > nl->nld_thr[nl->nld_i].i_hi) nl->hw_limit_hi = true; else nl->hw_limit_hi = false; } else { if (nl->hw < thr_lo) nl->hw_limit_lo = true; else nl->hw_limit_lo = false; if (nl->hw > (nl->hw_mask - thr_hi)) nl->hw_limit_hi = true; else nl->hw_limit_hi = false; } if (nl->hw == 0) nl->hw_limit_lo = true; if (nl->hw >= nl->hw_mask) nl->hw_limit_hi = true; /* reporting and thresholds */ if (nl->nld_i_change) { /* HW resolution just changed. Need thresholds and reporting * based on new settings. Reporting may not be this cycle due * to report_delay_min. */ nl->report = nl->cfg->report_n; } else { if (nl->thresholds_valid) { if (nl->hw < nl->hw_thresh_lo) nl->report = nl->cfg->report_n; else if (nl->hw > nl->hw_thresh_hi) nl->report = nl->cfg->report_n; } else { /* report everything if no thresholds */ nl->report = nl->cfg->report_n; } } ret = RET_NO_CHANGE; /* lux reporting */ if (nl->report && report_delay_min) { nl->report--; nl->timestamp_report = nl->timestamp; calc_f = 0; if (nl->cfg->scale.fval && !nl->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 * lux = HW * resolution * s * The NVS HAL will then convert the value to float * by multiplying the data with scale. */ if (nl->cfg->resolution.fval) { calc_f = (u64)nl->hw * nl->cfg->resolution.fval; do_div(calc_f, nl->cfg->scale.fval); } if (nl->cfg->resolution.ival) { if (nl->cfg->float_significance) calc_i = NVS_FLOAT_SIGNIFICANCE_NANO; else calc_i = NVS_FLOAT_SIGNIFICANCE_MICRO; do_div(calc_i, nl->cfg->scale.fval); calc_i *= (u64)nl->hw * nl->cfg->resolution.ival; } else { calc_i = 0; } } else { calc_i = nl->hw; } calc = (s64)(calc_i + calc_f); if (nl->calibration_en) /* when in calibration mode just return lux value */ nl->lux = (u32)calc; else /* get calibrated value if not in calibration mode */ nl->lux = nvs_light_interpolate(nl->cfg->uncal_lo, calc, nl->cfg->uncal_hi, nl->cfg->cal_lo, nl->cfg->cal_hi); if (nl->lux_max) { if (nl->lux > nl->lux_max) nl->lux = nl->lux_max; } /* report lux */ nl->handler(nl->nvs_st, &nl->lux, nl->timestamp_report); if (nl->thresholds_valid && !nl->report) { /* calculate low threshold */ calc = (s64)nl->hw; calc -= thr_lo; if (calc < 0) /* low threshold is disabled */ nl->hw_thresh_lo = 0; else nl->hw_thresh_lo = calc; /* calculate high threshold */ calc = nl->hw + thr_hi; if (calc > nl->hw_mask) /* high threshold is disabled */ nl->hw_thresh_hi = nl->hw_mask; else nl->hw_thresh_hi = calc; ret = RET_HW_UPDATE; } } /* dynamic resolution */ nl->nld_i_change = false; if (nl->nld_tbl) { /* if dynamic resolution is enabled */ /* adjust resolution if need to make room for thresholds */ if (nl->hw_limit_hi && nl->nld_i < nl->nld_i_hi) /* too many photons - decrease integration time */ ret = nvs_light_nld(nl, nl->nld_i + 1); else if (nl->hw_limit_lo && nl->nld_i > nl->nld_i_lo) /* not enough photons - increase integration time */ ret = nvs_light_nld(nl, nl->nld_i - 1); } /* poll time */ if (nl->nld_i_change) { nl->poll_delay_ms = nl->nld_tbl[nl->nld_i].delay_min_ms; } else { if (report_delay_min) poll_delay = nl->delay_us; if ((poll_delay < nl->cfg->delay_us_min) || nl->calibration_en) poll_delay = nl->cfg->delay_us_min; nl->poll_delay_ms = poll_delay / 1000; } if (nl->report || nl->calibration_en) ret = RET_POLL_NEXT; /* poll for next sample */ return ret; } EXPORT_SYMBOL_GPL(nvs_light_read); /** * nvs_light_enable - called when the light sensor is enabled. * @nl: the common structure between driver and common module. * * This inititializes the nl NVS variables. * * Returns 0 on success or a negative error code. */ int nvs_light_enable(struct nvs_light *nl) { if (!nl->cfg->report_n) nl->cfg->report_n = 1; nl->report = nl->cfg->report_n; nl->timestamp_report = 0; nl->hw_thresh_hi = 0; nl->hw_thresh_lo = -1; if (nl->nld_tbl) nvs_light_nld(nl, nl->nld_i_hi); nl->poll_delay_ms = nl->cfg->delay_us_min / 1000; if (nl->cfg->scale.ival == 1 && !nl->cfg->scale.fval) nl->calibration_en = true; else nl->calibration_en = false; return 0; } EXPORT_SYMBOL_GPL(nvs_light_enable); /** * nvs_light_of_dt - called during system boot to acquire * dynamic resolution table index limits. * @nl: the common structure between driver and common module. * @np: device node pointer. * @dev_name: device name string. Typically a string to "light" * or NULL. * * Returns 0 on success or a negative error code. * * Driver must initialize variables if no success. * NOTE: DT must have both indexes for a success. */ int nvs_light_of_dt(struct nvs_light *nl, const struct device_node *np, const char *dev_name) { bool nld_thr_disable; char str[256]; unsigned int i; int ret; int ret_t; if (!nl->cfg) return -EINVAL; nl->cfg->flags |= SENSOR_FLAG_ON_CHANGE_MODE; if (np == NULL) return -EINVAL; if (dev_name == NULL) dev_name = NVS_LIGHT_STRING; if (nl->nld_thr) { /* nl->nld_tbl_n == 0 is allowed in case HW driver provides * hardcoded values in nl->nld_thr */ if (nl->nld_tbl_n) nld_thr_disable = true; else nld_thr_disable = false; for (i = 0; i < nl->nld_tbl_n; i++) { nl->nld_thr[i].lo = nl->cfg->thresh_lo; nl->nld_thr[i].i_lo = nl->cfg->thresh_lo; ret = snprintf(str, sizeof(str), "%s_nld_thr_lo_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].lo); if (!ret) nld_thr_disable = false; } ret = snprintf(str, sizeof(str), "%s_nld_thr_i_lo_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].i_lo); if (!ret) nld_thr_disable = false; } nl->nld_thr[i].hi = nl->cfg->thresh_hi; nl->nld_thr[i].i_hi = nl->hw_mask - nl->cfg->thresh_hi; ret = snprintf(str, sizeof(str), "%s_nld_thr_hi_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].hi); if (!ret) nld_thr_disable = false; } ret = snprintf(str, sizeof(str), "%s_nld_thr_i_hi_%u", dev_name, i); if (ret > 0) { ret = of_property_read_u32(np, str, &nl->nld_thr[i].i_hi); if (!ret) nld_thr_disable = false; } } if (nld_thr_disable) /* there isn't a DT entry so disable this feature */ nl->nld_thr = NULL; } ret = snprintf(str, sizeof(str), "%s_lux_maximum", dev_name); if (ret > 0) of_property_read_u32(np, str, &nl->lux_max); ret_t = -EINVAL; ret = snprintf(str, sizeof(str), "%s_dynamic_resolution_index_limit_low", dev_name); if (ret > 0) ret_t = of_property_read_u32(np, str, &nl->nld_i_lo); ret = snprintf(str, sizeof(str), "%s_dynamic_resolution_index_limit_high", dev_name); if (ret > 0) ret_t |= of_property_read_u32(np, str, &nl->nld_i_hi); if (nl->nld_i_hi < nl->nld_i_lo) return -EINVAL; return ret_t; } EXPORT_SYMBOL_GPL(nvs_light_of_dt); /** * nvs_light_resolution - runtime mechanism to modify nld_i_lo. * @nl: the common structure between driver and common module. * @resolution: an integer value that will be nld_i_lo. * * Returns 0 on success or a negative error code if a dynamic * resolution table exists. Otherwise a 1 is returned. * * NOTE: A returned 1 allows the NVS layer above this one to * simply store the new resolution value. * NOTE: Caller can check nld_i_change to update the HW with the * new indexed values. */ int nvs_light_resolution(struct nvs_light *nl, int resolution) { if (nl->nld_tbl == NULL) return 1; if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } if (resolution < 0 || resolution >= nl->nld_tbl_n) { pr_err("%s ERR: nld_i_lo (%d) out of range (0-%u)\n", __func__, resolution, nl->nld_tbl_n - 1); return -EINVAL; } if (resolution > nl->nld_i_hi) { pr_err("%s ERR: nld_i_lo (%d) > nld_i_hi (%u)\n", __func__, resolution, nl->nld_i_hi); return -EINVAL; } nl->nld_i_lo = resolution; if (nl->nld_i < nl->nld_i_lo) nvs_light_nld(nl, nl->nld_i_lo); return 0; } EXPORT_SYMBOL_GPL(nvs_light_resolution); /** * nvs_light_max_range - runtime mechanism to modify nld_i_hi. * @nl: the common structure between driver and common module. * @max_range: an integer value that will be nld_i_hi. * * Returns 0 on success or a negative error code if a dynamic * resolution table exists. Otherwise a 1 is returned. * * NOTE: A returned 1 allows the NVS layer above this one to * simply store the new max_range value. * NOTE: Caller can check nld_i_change to update the HW with the * new indexed values. */ int nvs_light_max_range(struct nvs_light *nl, int max_range) { if (nl->nld_tbl == NULL) return 1; if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } if (max_range < 0 || max_range >= nl->nld_tbl_n) { pr_err("%s ERR: nld_i_hi (%d) out of range (0-%u)\n", __func__, max_range, nl->nld_tbl_n - 1); return -EINVAL; } if (max_range < nl->nld_i_lo) { pr_err("%s ERR: nld_i_hi (%d) < nld_i_lo (%u)\n", __func__, max_range, nl->nld_i_lo); return -EINVAL; } nl->nld_i_hi = max_range; if (nl->nld_i > nl->nld_i_hi) nvs_light_nld(nl, nl->nld_i_hi); return 0; } EXPORT_SYMBOL_GPL(nvs_light_max_range); /** * nvs_light_threshold_calibrate_lo - runtime mechanism to * modify calibrated/uncalibrated low value. * @nl: the common structure between driver and common module. * @lo: either cal_lo or thresh_lo. * * Returns 0 on success or a negative error code. * * NOTE: If not in calibration mode then thresholds are modified * instead. */ int nvs_light_threshold_calibrate_lo(struct nvs_light *nl, int lo) { bool i_thr; unsigned int i; if (nl->calibration_en) { nl->cfg->uncal_lo = nl->lux; nl->cfg->cal_lo = lo; } else { if (nl->nld_thr) { if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } i = lo >> NVS_LIGHT_THRESH_CMD_SHIFT; i_thr = (bool)(i & NVS_LIGHT_THRESH_CMD_SEL_MSK); i &= NVS_LIGHT_THRESH_CMD_NDX_MSK; if (i >= nl->nld_tbl_n) { pr_err("%s ERR: index %d > %u\n", __func__, i, nl->nld_tbl_n - 1); return -EINVAL; } if (i_thr) nl->nld_thr[i].i_lo = lo & nl->hw_mask; else nl->nld_thr[i].lo = lo & nl->hw_mask; } else { nl->cfg->thresh_lo = lo; } } return 0; } EXPORT_SYMBOL_GPL(nvs_light_threshold_calibrate_lo); /** * nvs_light_threshold_calibrate_hi - runtime mechanism to * modify calibrated/uncalibrated high value. * @nl: the common structure between driver and common module. * @hi: either cal_hi or thresh_hi. * * Returns 0 on success or a negative error code. * * NOTE: If not in calibration mode then thresholds are modified * instead. */ int nvs_light_threshold_calibrate_hi(struct nvs_light *nl, int hi) { bool i_thr; unsigned int i; if (nl->calibration_en) { nl->cfg->uncal_hi = nl->lux; nl->cfg->cal_hi = hi; } else { if (nl->nld_thr) { if (!nl->nld_tbl_n) { pr_err("%s ERR: feature not supported\n", __func__); return -EINVAL; } i = hi >> NVS_LIGHT_THRESH_CMD_SHIFT; i_thr = (bool)(i & NVS_LIGHT_THRESH_CMD_SEL_MSK); i &= NVS_LIGHT_THRESH_CMD_NDX_MSK; if (i >= nl->nld_tbl_n) { pr_err("%s ERR: index %u > %u\n", __func__, i, nl->nld_tbl_n - 1); return -EINVAL; } if (i_thr) nl->nld_thr[i].i_hi = hi & nl->hw_mask; else nl->nld_thr[i].hi = hi & nl->hw_mask; } else { nl->cfg->thresh_hi = hi; } } return 0; } EXPORT_SYMBOL_GPL(nvs_light_threshold_calibrate_hi); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("NVidia Sensor light module"); MODULE_AUTHOR("NVIDIA Corporation");