[PATCH v2 1/4] iio: light: tsl2591: don't divide by zero
From: Jan Remes
Date: Wed Oct 14 2026 - 10:42:54 EST
From: Jan Remes <jan.remes@xxxxxxxxxxxxxxx>
The TSL2591 has two raw light channels - CH0 and CH1. The algorithm for
calculating illumination from the raw channels requires division by CH0
(which can be zero) and by "counts per lux" (CPL) value which is also zero
for (gain = LOW, integration time <= 400 ms).
Furthermore, the algorithm assumes CH0 > CH1, which can be violated in
almost complete darkness.
As a result, reading from 'in_illuminance_input' results in
division-by-zero whenever CH0 is 0 (complete darkness) or whenever the
parameters result in CPL being zero. This results in kernel oops on
x86_64 and in reading the value as 0 on arm64.
Fix that by:
- reporting CH0 <= CH1 as zero (catches also CH0 = 0)
- updating the lux algorithm to only do the division at the end
Fixes: 2335f0d7c790 ("iio: light: Added AMS tsl2591 driver implementation")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Martin Triska <martin.triska@xxxxxxxxxxxxxxx>
Assisted-by: LLM
Signed-off-by: Jan Remes <jan.remes@xxxxxxxxxxxxxxx>
---
drivers/iio/light/tsl2591.c | 32 ++++++++++++++++++++++----------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/light/tsl2591.c b/drivers/iio/light/tsl2591.c
index ef3ed9635a1e..f56d31404c25 100644
--- a/drivers/iio/light/tsl2591.c
+++ b/drivers/iio/light/tsl2591.c
@@ -16,6 +16,7 @@
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_runtime.h>
@@ -362,6 +363,10 @@ static int tsl2591_wait_adc_complete(struct tsl2591_chip *chip)
* Scale values to get more representative value of lux i.e.
* lux = ((C0DATA - C1DATA) * (1000 - ((C1DATA * 1000) / C0DATA))) / CPL
*
+ * CPL is not computed on its own: at 1x gain it is below 1 for integration
+ * times under 500 ms, so the division by LUX DF is folded into the final
+ * division instead.
+ *
* Channel 0 = IR + Visible
* Channel 1 = IR only
*/
@@ -373,8 +378,10 @@ static int tsl2591_read_channel_data(struct iio_dev *indio_dev,
struct tsl2591_als_settings *settings = &chip->als_settings;
struct i2c_client *client = chip->client;
u8 als_data[TSL2591_NUM_DATA_REGISTERS];
- int counts_per_lux, int_time_fval, gain_multi, lux;
+ int int_time_fval, gain_multi;
u16 als_ch0, als_ch1;
+ u64 lux;
+ u32 rem;
int ret;
ret = tsl2591_wait_adc_complete(chip);
@@ -410,23 +417,28 @@ static int tsl2591_read_channel_data(struct iio_dev *indio_dev,
return gain_multi;
}
- int_time_fval = TSL2591_FVAL_TO_MSEC(settings->als_int_time);
- /* Calculate counts per lux value */
- counts_per_lux = (int_time_fval * gain_multi) / TSL2591_LUX_COEFFICIENT;
+ /* No visible light, this also catches C0DATA == 0 */
+ if (als_ch0 <= als_ch1) {
+ *val = 0;
+ *val2 = 0;
+ break;
+ }
- dev_dbg(&client->dev, "Counts Per Lux: %d\n", counts_per_lux);
+ int_time_fval = TSL2591_FVAL_TO_MSEC(settings->als_int_time);
/* Calculate lux value */
- lux = ((als_ch0 - als_ch1) *
- (1000 - ((als_ch1 * 1000) / als_ch0))) / counts_per_lux;
+ lux = (u64)(als_ch0 - als_ch1) *
+ (1000 - ((als_ch1 * 1000) / als_ch0)) *
+ TSL2591_LUX_COEFFICIENT;
+ lux = div_u64(lux, int_time_fval * gain_multi);
- dev_dbg(&client->dev, "Raw lux calculation: %d\n", lux);
+ dev_dbg(&client->dev, "Raw lux calculation: %llu\n", lux);
/* Divide by 1000 to get real lux value before scaling */
- *val = lux / 1000;
+ *val = div_u64_rem(lux, 1000, &rem);
/* Get the decimal part of lux reading */
- *val2 = (lux - (*val * 1000)) * 1000;
+ *val2 = rem * 1000;
break;
default:
--
2.53.0