From patchwork Wed Feb 22 16:14:25 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149333 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D669CC61DA4 for ; Wed, 22 Feb 2023 16:14:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232649AbjBVQOk (ORCPT ); Wed, 22 Feb 2023 11:14:40 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34584 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230406AbjBVQOj (ORCPT ); Wed, 22 Feb 2023 11:14:39 -0500 Received: from mail-lf1-x12f.google.com (mail-lf1-x12f.google.com [IPv6:2a00:1450:4864:20::12f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 9B1415BBD; Wed, 22 Feb 2023 08:14:33 -0800 (PST) Received: by mail-lf1-x12f.google.com with SMTP id s22so10613433lfi.9; Wed, 22 Feb 2023 08:14:33 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=l5JBDHjdN3MXwiI4IvjYTThifirf1TjzCClQnfsSE+U=; b=C4BKH4vzq1IF/vkpPcXc7b3H47gB9h+zS9yBv4/O8gLdjbjqoFn8PQJZgXhgmqakTC BpE3h/J0uiTvINLApNIwOethrvNsMtNikrgXePg+2LV0PMy/KxV/iw8da8FKuyVYk6aJ HFkFjM3BsXMFkr860SpA1ExHxyADvZTiWipbB6nm8Gk6zjfekwOslY8jtRgjkMiBO8h1 2jOJ2bMMjrR4yXr6ecCqsElNCb2mI70NhUNCOpFJE/fEtlRP0ePAb/d2pWC7W50CLaPY 0iIN1G8mIYd2/QdOpjkHiQ5ShGaMDXXoN8f0hcWs1Ag93sIq7z+AAZWXtx6ONpU6mlqZ VxiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=l5JBDHjdN3MXwiI4IvjYTThifirf1TjzCClQnfsSE+U=; b=HoLQwuKUfEHNRzpljJUtYv/htr3/R/6RFIqRoI38pdbRyW/siMix+LPip7n6CiYYMU S9GLko0tWlcMMy5hgfwr8Fo1CV+pAzWIKgCZhQSvafy/uq6xjulYBV/uMtBoC35ltW8T CCfSZusqhvYa/NnUC3//iilrBZHel35V2rpCgCmyKQHSts10SFu2W5l2a6BNN4j4BM95 4pflj3/rt7idD6cELRNwP5l44dlRGF6xIGnjxbAiVAIYepH3ED4fnMeU5Km8/xaycez6 lFNQiojizIiDKXUeKAlzhAvRoxvcp7NUkC4PLmAKz82zduCsQkNF+TwjQl6yZQQDZugT BvGw== X-Gm-Message-State: AO0yUKW4VP+TT1RpKDXwkvJ8snhUJpn9EkKTdhQeun4Mxizo0H/K1qLH /MVW9As8oUOlyhXRJooOJFk= X-Google-Smtp-Source: AK7set/cyqYcHBeoYVP4mLuHnE2LUNKTG5gTdN+PU5U9ATeZRhRW5bSSbnQpK3fzyEOWvFXjATIhKg== X-Received: by 2002:a19:c511:0:b0:4b5:90c5:281c with SMTP id w17-20020a19c511000000b004b590c5281cmr3400591lfe.19.1677082470939; Wed, 22 Feb 2023 08:14:30 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id y10-20020a19750a000000b004d885a44789sm2211833lfe.66.2023.02.22.08.14.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:14:30 -0800 (PST) Date: Wed, 22 Feb 2023 18:14:25 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 1/6] dt-bindings: iio: light: Support ROHM BU27034 Message-ID: References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes capable of detecting a very wide range of illuminance. Typical application is adjusting LCD and backlight power of TVs and mobile phones. Add initial dt-bindings. Signed-off-by: Matti Vaittinen Reviewed-by: Krzysztof Kozlowski --- .../bindings/iio/light/rohm-bu27034.yaml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/rohm-bu27034.yaml diff --git a/Documentation/devicetree/bindings/iio/light/rohm-bu27034.yaml b/Documentation/devicetree/bindings/iio/light/rohm-bu27034.yaml new file mode 100644 index 000000000000..a3a642c259e8 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/rohm-bu27034.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/rohm-bu27034.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ROHM BU27034 ambient light sensor + +maintainers: + - Matti Vaittinen + +description: | + ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes + capable of detecting a very wide range of illuminance. Typical application + is adjusting LCD and backlight power of TVs and mobile phones. + https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/light/bu27034nuc-e.pdf + +properties: + compatible: + const: rohm,bu27034 + + reg: + maxItems: 1 + + vdd-supply: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + light-sensor@38 { + compatible = "rohm,bu27034"; + reg = <0x38>; + vdd-supply = <&vdd>; + }; + }; + +... From patchwork Wed Feb 22 16:14:45 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149334 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 61300C61DA4 for ; Wed, 22 Feb 2023 16:15:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232725AbjBVQPE (ORCPT ); Wed, 22 Feb 2023 11:15:04 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35088 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232711AbjBVQO6 (ORCPT ); Wed, 22 Feb 2023 11:14:58 -0500 Received: from mail-lf1-x134.google.com (mail-lf1-x134.google.com [IPv6:2a00:1450:4864:20::134]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E75103D08A; Wed, 22 Feb 2023 08:14:52 -0800 (PST) Received: by mail-lf1-x134.google.com with SMTP id f41so10683733lfv.13; Wed, 22 Feb 2023 08:14:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=JBzj6zg5K+yTYjmvzeLzRMbBCszvayJl3H3HpOt/nlw=; b=UC3vEvPke7M9kkglNYtM39AXQJRkiNClmclSfsMZQEsoh7gxALSRu5SvRLQvnaK6ff 60x30fge/XG9sNwxPGDU/fH1+REed0vVFm7BVYGMi8/yQRCZIGYOduZFdRSWpzW+flKg L2SXNyTxzIhP71ThED2pFZwSL6MoePM0j1nlSAKhRWYcV9zbEVnyrW0hamNs+6iLzNi8 h7t9xPQE6FHggbCHcKEvK0fY0VE9LSbAbNnYsZ+VSfF/UETLu2eoqEFOk2UnT/YR4aUP oqwTjiE95tnIrBiVZTLZVPycF3ujklrxUEl4fbsU5CcKzn8ZRrZ7//nQsL0uWgIuhzOG scjA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=JBzj6zg5K+yTYjmvzeLzRMbBCszvayJl3H3HpOt/nlw=; b=XuzdpGWpcJk3FhPuUXCKHon7oHLK40twFoHptypyS5xdJp3LDPWVtx8pN/gvBjy+Oo Fn3UfOAKISkaqJoOz/78HL4j7Ac/8vKhVLG1xYHv02/kTgy9qtvEnjx5vzdIvYFlknGh +HbDlCNqB9MHNGdX+E77FffDKFJXwmJSld/9lKTs8Xbp/f+rX9i4Z4V9wc3SZD+le/2c nR1Pe53Yni0wktpjLD9rBvdPVbLrBpbBBNXfTcyMhTSrg/SJ04u94WlBz7gmhL+WVu3O TuFrwU4WeFjLXWMQfWw17bBMow+FiS2Kn9BQ8mAPLokUhb1m4U47gQmcL4TpksSOsV8h nZMg== X-Gm-Message-State: AO0yUKVKzTUn/Ou0bpt6Sv4wKHTZ1qq+YdYDYTOzEkMAmRcf8g0X2XxA Kq/4qH4sv26jpLJGJebEVEs= X-Google-Smtp-Source: AK7set/78fNy14VeGoLWeil8OIdWMpZPum2AXVgVeC14qu4fzwGvVabcmRYiKwUTClEKQOPleH/KNA== X-Received: by 2002:ac2:51b3:0:b0:4d8:8ad1:a05e with SMTP id f19-20020ac251b3000000b004d88ad1a05emr2266672lfk.52.1677082490747; Wed, 22 Feb 2023 08:14:50 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id q10-20020ac25a0a000000b004dc4b0ca734sm726053lfn.250.2023.02.22.08.14.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:14:49 -0800 (PST) Date: Wed, 22 Feb 2023 18:14:45 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 2/6] iio: light: Add gain-time-scale helpers Message-ID: <3de52eafbf085e8f0048647a1cc01cb73842f367.1677080089.git.mazziesaccount@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org Some light sensors can adjust both the HW-gain and integration time. There are cases where adjusting the integration time has similar impact to the scale of the reported values as gain setting has. IIO users do typically expect to handle scale by a single writable 'scale' entry. Driver should then adjust the gain/time accordingly. It however is difficult for a driver to know whether it should change gain or integration time to meet the requested scale. Usually it is preferred to have longer integration time which usually improves accuracy, but there may be use-cases where long measurement times can be an issue. Thus it can be preferable to allow also changing the integration time - but mitigate the scale impact by also changing the gain underneath. Eg, if integration time change doubles the measured values, the driver can reduce the HW-gain to half. The theory of the computations of gain-time-scale is simple. However, some people (undersigned) got that implemented wrong for more than once. Add some gain-time-scale helpers in order to not dublicate errors in all drivers needing these computations. Signed-off-by: Matti Vaittinen --- Currently it is only BU27034 using these in this series. I am however working with drivers for RGB sensors BU27008 and BU27010 which have similar [gain - integration time - scale] - relation. I hope sending those follows soon after the BU27034 is done. --- drivers/iio/light/Kconfig | 3 + drivers/iio/light/Makefile | 1 + drivers/iio/light/gain-time-scale-helper.c | 446 +++++++++++++++++++++ drivers/iio/light/gain-time-scale-helper.h | 111 +++++ 4 files changed, 561 insertions(+) create mode 100644 drivers/iio/light/gain-time-scale-helper.c create mode 100644 drivers/iio/light/gain-time-scale-helper.h diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 0d4447df7200..671d84f98c56 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -183,6 +183,9 @@ config IIO_CROS_EC_LIGHT_PROX To compile this driver as a module, choose M here: the module will be called cros_ec_light_prox. +config IIO_GTS_HELPER + tristate + config GP2AP002 tristate "Sharp GP2AP002 Proximity/ALS sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 6f23817fae6f..f4705fac7a96 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_CM3323) += cm3323.o obj-$(CONFIG_CM3605) += cm3605.o obj-$(CONFIG_CM36651) += cm36651.o obj-$(CONFIG_IIO_CROS_EC_LIGHT_PROX) += cros_ec_light_prox.o +obj-$(CONFIG_IIO_GTS_HELPER) += gain-time-scale-helper.o obj-$(CONFIG_GP2AP002) += gp2ap002.o obj-$(CONFIG_GP2AP020A00F) += gp2ap020a00f.o obj-$(CONFIG_HID_SENSOR_ALS) += hid-sensor-als.o diff --git a/drivers/iio/light/gain-time-scale-helper.c b/drivers/iio/light/gain-time-scale-helper.c new file mode 100644 index 000000000000..bd8fc11802ee --- /dev/null +++ b/drivers/iio/light/gain-time-scale-helper.c @@ -0,0 +1,446 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* gain-time-scale conversion helpers for IIO light sensors + * + * Copyright (c) 2023 Matti Vaittinen + */ + +#include +#include +#include +#include + +#include "gain-time-scale-helper.h" + +static int iio_gts_get_gain(const u64 max, u64 scale) +{ + int tmp = 1; + + if (scale > max || !scale) + return -EINVAL; + + if (0xffffffffffffffffLLU - max < scale) { + /* Risk of overflow */ + if (max - scale < scale) + return 1; + + while (max - scale > scale * (u64) tmp) + tmp++; + + return tmp + 1; + } + + while (max > scale * (u64) tmp) + tmp++; + + return tmp; +} + +static int gain_get_scale_fraction(const u64 max, u64 scale, int known, + int *unknown) +{ + int tot_gain; + + if (!known) + return -EINVAL; + + tot_gain = iio_gts_get_gain(max, scale); + if (tot_gain < 0) + return tot_gain; + + *unknown = tot_gain/known; + + /* We require total gain to be exact multiple of known * unknown */ + if (!*unknown || *unknown * known != tot_gain) + return -EINVAL; + + return 0; +} + +static const struct iio_itime_sel_mul * + iio_gts_find_itime_by_time(struct iio_gts *gts, int time) +{ + int i; + + if (!gts->num_itime) + return NULL; + + for (i = 0; i < gts->num_itime; i++) + if (gts->itime_table[i].time_us == time) + return >s->itime_table[i]; + + return NULL; +} + +static const struct iio_itime_sel_mul * + iio_gts_find_itime_by_sel(struct iio_gts *gts, int sel) +{ + int i; + + if (!gts->num_itime) + return NULL; + + for (i = 0; i < gts->num_itime; i++) + if (gts->itime_table[i].sel == sel) + return >s->itime_table[i]; + + return NULL; +} + +static int iio_gts_delinearize(u64 lin_scale, int *scale_whole, int *scale_nano, + unsigned long scaler) +{ + int frac; + + if (scaler > NANO || !scaler) + return -EINVAL; + + frac = do_div(lin_scale, scaler); + + *scale_whole = lin_scale; + *scale_nano = frac * (NANO / scaler); + + return 0; +} + +static int iio_gts_linearize(int scale_whole, int scale_nano, u64 *lin_scale, + unsigned long scaler) +{ + /* + * Expect scale to be (mostly) NANO or MICRO. Divide divider instead of + * multiplication followed by division to avoid overflow + */ + if (scaler > NANO || !scaler) + return -EINVAL; + + *lin_scale = (u64) scale_whole * (u64)scaler + (u64)(scale_nano + / (NANO / scaler)); + + return 0; +} + +/** + * iio_init_iio_gts - Initialize the gain-time-scale helper + * @max_scale_int: integer part of the maximum scale value + * @max_scale_nano: fraction part of the maximum scale value + * @gain_tbl: table describing supported gains + * @num_gain: number of gains in the gaintable + * @tim_tbl: table describing supported integration times + * @num_times: number of times in the time table + * @gts: pointer to the helper struct + * + * Initialize the gain-time-scale helper for use. Please, provide the + * integration time table sorted so that the preferred integration time is + * in the first array index. The search functions like the + * iio_gts_find_time_and_gain_sel_for_scale() start search from first + * provided time. + * + * Return: 0 on success. + */ +int iio_init_iio_gts(int max_scale_int, int max_scale_nano, + const struct iio_gain_sel_pair *gain_tbl, int num_gain, + const struct iio_itime_sel_mul *tim_tbl, int num_times, + struct iio_gts *gts) +{ + int ret; + + ret = iio_gts_linearize(max_scale_int, max_scale_nano, + >s->max_scale, NANO); + if (ret) + return ret; + + gts->hwgain_table = gain_tbl; + gts->num_hwgain = num_gain; + gts->itime_table = tim_tbl; + gts->num_itime = num_times; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_init_iio_gts); + +bool iio_gts_valid_time(struct iio_gts *gts, int time_us) +{ + return !!iio_gts_find_itime_by_time(gts, time_us); +} +EXPORT_SYMBOL_GPL(iio_gts_valid_time); + +int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain) +{ + int i; + + for (i = 0; i < gts->num_hwgain; i++) + if (gts->hwgain_table[i].gain == gain) + return gts->hwgain_table[i].sel; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(iio_gts_find_sel_by_gain); + +bool iio_gts_valid_gain(struct iio_gts *gts, int gain) +{ + return !(iio_gts_find_sel_by_gain(gts, gain) < 0); +} +EXPORT_SYMBOL_GPL(iio_gts_valid_gain); + +int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel) +{ + int i; + + for (i = 0; i < gts->num_hwgain; i++) + if (gts->hwgain_table[i].sel == sel) + return gts->hwgain_table[i].gain; + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(iio_gts_find_gain_by_sel); + +int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts, + int sel) +{ + const struct iio_itime_sel_mul *time; + + time = iio_gts_find_itime_by_sel(gts, sel); + if (!time) + return -EINVAL; + + return time->mul; +} +EXPORT_SYMBOL_GPL(iio_gts_get_int_time_gain_multiplier_by_sel); + +/** + * iio_gts_find_gain_for_scale_using_time - Find gain by time and scale + * @gts: Gain time scale descriptor + * @time_sel: Integration time selector correspondig to the time gain is + * searhed for + * @scale_int: Integral part of the scale (typically val1) + * @scale_nano: Fractional part of the scale (nano or ppb) + * @gain: Pointer to value where gain is stored. + * + * In some cases the light sensors may want to find a gain setting which + * corresponds given scale and integration time. Sensors which fill the + * gain and time tables may use this helper to retrieve the gain. + * + * Return: 0 on success. -EINVAL if gain matching the parameters is not + * found. + */ +int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain) +{ + u64 scale_linear; + int ret, mul; + + ret = iio_gts_linearize(scale_int, scale_nano, &scale_linear, NANO); + if (ret) + return ret; + + ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, time_sel); + if (ret < 0) + return ret; + + mul = ret; + + ret = gain_get_scale_fraction(gts->max_scale, scale_linear, mul, gain); + + if (ret || !iio_gts_valid_gain(gts, *gain)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_gts_find_gain_for_scale_using_time); + +/* + * iio_gts_find_gain_sel_for_scale_using_time - Fetch gain selector. + * See iio_gts_find_gain_for_scale_using_time() for more information + */ +int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain_sel) +{ + int gain, ret; + + ret = iio_gts_find_gain_for_scale_using_time(gts, time_sel, scale_int, + scale_nano, &gain); + if (ret) + return ret; + + ret = iio_gts_find_sel_by_gain(gts, gain); + if (ret < 0) + return ret; + + *gain_sel = ret; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_gts_find_gain_sel_for_scale_using_time); + +int iio_gts_find_time_and_gain_sel_for_scale(struct iio_gts *gts, int scale_int, + int scale_nano, int *gain_sel, + int *time_sel) +{ + int ret, i; + + for (i = 0; i < gts->num_itime; i++) { + *time_sel = gts->itime_table[i].sel; + ret = iio_gts_find_gain_sel_for_scale_using_time(gts, *time_sel, + scale_int, scale_nano, gain_sel); + if (!ret) + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(iio_gts_find_time_and_gain_sel_for_scale); + +int iio_gts_find_int_time_by_sel(struct iio_gts *gts, int sel) +{ + const struct iio_itime_sel_mul *itime; + + itime = iio_gts_find_itime_by_sel(gts, sel); + if (!itime) + return -EINVAL; + + return itime->time_us; +} +EXPORT_SYMBOL_GPL(iio_gts_find_int_time_by_sel); + +int iio_gts_find_sel_by_int_time(struct iio_gts *gts, int time) +{ + const struct iio_itime_sel_mul *itime; + + itime = iio_gts_find_itime_by_time(gts, time); + if (!itime) + return -EINVAL; + + return itime->sel; +} +EXPORT_SYMBOL_GPL(iio_gts_find_sel_by_int_time); + +int iio_gts_get_total_gain_by_sel(struct iio_gts *gts, int gsel, int tsel) +{ + int ret, tmp; + + ret = iio_gts_find_gain_by_sel(gts, gsel); + if (ret < 0) + return ret; + + tmp = ret; + + /* + * TODO: Would these helpers provde any value for cases where we just + * use table of gains and no integration time? This would be a standard + * format for gain table representation and regval => gain / gain => + * regval conversions. OTOH, a dummy table based conversion is a memory + * hog in cases where the gain could be computed simply based on simple + * multiplication / bit-shift or by linear_ranges helpers. + * + * Currently we return an error if int-time table is not populated. + */ + ret = iio_gts_get_int_time_gain_multiplier_by_sel(gts, tsel); + if (ret < 0) + return ret; + + return tmp * ret; +} +EXPORT_SYMBOL_GPL(iio_gts_get_total_gain_by_sel); + +int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time) +{ + const struct iio_itime_sel_mul *itime; + + if (!iio_gts_valid_gain(gts, gain)) + return -EINVAL; + + if (!gts->num_itime) + return gain; + + itime = iio_gts_find_itime_by_time(gts, time); + if (!itime) + return -EINVAL; + + return gain * itime->mul; +} +EXPORT_SYMBOL(iio_gts_get_total_gain); + +static int iio_gts_get_scale_linear(struct iio_gts *gts, int gain, int time, + u64 *scale) +{ + int total_gain; + u64 tmp; + + total_gain = iio_gts_get_total_gain(gts, gain, time); + if (total_gain < 0) + return total_gain; + + tmp = gts->max_scale; + + do_div(tmp, total_gain); + + *scale = tmp; + + return 0; +} + +int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int, + int *scale_nano) +{ + u64 lin_scale; + int ret; + + ret = iio_gts_get_scale_linear(gts, gain, time, &lin_scale); + if (ret) + return ret; + + return iio_gts_delinearize(lin_scale, scale_int, scale_nano, NANO); +} +EXPORT_SYMBOL_GPL(iio_gts_get_scale); + +/** + * iio_gts_find_new_gain_sel_by_old_gain_time - compensate time change + * @gts: Gain time scale descriptor + * @old_gain: Previously set gain + * @old_time_sel: Selector corresponding previously set time + * @new_time_sel: Selector corresponding new time to be set + * @new_gain: Pointer to value where new gain is to be written + * + * We may want to mitigate the scale change caused by setting a new integration + * time (for a light sensor) by also updating the (HW)gain. This helper computes + * new gain value to maintain the scale with new integration time. + * + * Return: 0 on success. -EINVAL if gain matching the new time is not found. + */ +int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts, + int old_gain, int old_time_sel, + int new_time_sel, int *new_gain) +{ + const struct iio_itime_sel_mul *itime_old, *itime_new; + u64 scale; + int ret; + + itime_old = iio_gts_find_itime_by_sel(gts, old_time_sel); + if (!itime_old) + return -EINVAL; + + itime_new = iio_gts_find_itime_by_sel(gts, new_time_sel); + if (!itime_new) + return -EINVAL; + + ret = iio_gts_get_scale_linear(gts, old_gain, itime_old->time_us, + &scale); + if (ret) + return ret; + + ret = gain_get_scale_fraction(gts->max_scale, scale, itime_new->mul, + new_gain); + if (ret) + return -EINVAL; + + if (!iio_gts_valid_gain(gts, *new_gain)) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(iio_gts_find_new_gain_sel_by_old_gain_time); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("IIO light sensor gain-time-scale helpers"); diff --git a/drivers/iio/light/gain-time-scale-helper.h b/drivers/iio/light/gain-time-scale-helper.h new file mode 100644 index 000000000000..70a952a8de92 --- /dev/null +++ b/drivers/iio/light/gain-time-scale-helper.h @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* gain-time-scale conversion helpers for IIO light sensors + * + * Copyright (c) 2023 Matti Vaittinen + */ + +#ifndef __GAIN_TIME_SCALE_HELPER__ +#define ___GAIN_TIME_SCALE_HELPER__ + +/** + * struct iio_gain_sel_pair - gain - selector values + * + * In many cases devices like light sensors allow setting signal amplification + * (gain) using a register interface. This structure describes amplification + * and corresponding selector (register value) + * + * @gain: Gain (multiplication) value. + * @sel: Selector (usually register value) used to indicate this gain + */ +struct iio_gain_sel_pair { + int gain; + int sel; +}; + +/** + * struct iio_itime_sel_mul - integration time description + * + * In many cases devices like light sensors allow setting the duration of + * collecting data. Typically this duration has also an impact to the magnitude + * of measured values (gain). This structure describes the relation of + * integration time and amplification as well as corresponding selector + * (register value). + * + * An example could be a sensor allowing 50, 100, 200 and 400 mS times. The + * respective multiplication values could be 50 mS => 1, 100 mS => 2, + * 200 mS => 4 and 400 mS => 8 assuming the impact of integration time would be + * linear in a way that when collecting data for 50 mS caused value X, doubling + * the data collection time caused value 2X etc.. + * + * @time_us: Integration time in microseconds. + * @sel: Selector (usually register value) used to indicate this time + * @mul: Multiplication to the values caused by this time. + */ +struct iio_itime_sel_mul { + int time_us; + int sel; + int mul; +}; + +struct iio_gts { + u64 max_scale; + const struct iio_gain_sel_pair *hwgain_table; + int num_hwgain; + const struct iio_itime_sel_mul *itime_table; + int num_itime; +}; + +#define GAIN_SCALE_GAIN(_gain, _sel) \ +{ \ + .gain = (_gain), \ + .sel = (_sel), \ +} + +#define GAIN_SCALE_ITIME_MS(_itime, _sel, _mul) \ +{ \ + .time_us = (_itime) * 1000, \ + .sel = (_sel), \ + .mul = (_mul), \ +} + +#define GAIN_SCALE_ITIME_US(_itime, _sel, _mul) \ +{ \ + .time_us = (_itime), \ + .sel = (_sel), \ + .mul = (_mul), \ +} + +int iio_init_iio_gts(int max_scale_int, int max_scale_nano, + const struct iio_gain_sel_pair *gain_tbl, int num_gain, + const struct iio_itime_sel_mul *tim_tbl, int num_times, + struct iio_gts *gts); + +bool iio_gts_valid_gain(struct iio_gts *gts, int gain); +bool iio_gts_valid_time(struct iio_gts *gts, int time_us); + +int iio_gts_get_total_gain_by_sel(struct iio_gts *gts, int gsel, int tsel); +int iio_gts_get_total_gain(struct iio_gts *gts, int gain, int time); + +int iio_gts_find_gain_by_sel(struct iio_gts *gts, int sel); +int iio_gts_find_sel_by_gain(struct iio_gts *gts, int gain); +int iio_gts_find_int_time_by_sel(struct iio_gts *gts, int sel); +int iio_gts_find_sel_by_int_time(struct iio_gts *gts, int time); + +int iio_gts_get_int_time_gain_multiplier_by_sel(struct iio_gts *gts, + int sel); +int iio_gts_find_gain_sel_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain_sel); +int iio_gts_find_gain_for_scale_using_time(struct iio_gts *gts, int time_sel, + int scale_int, int scale_nano, + int *gain); +int iio_gts_find_time_and_gain_sel_for_scale(struct iio_gts *gts, int scale_int, + int scale_nano, int *gain_sel, + int *time_sel); +int iio_gts_get_scale(struct iio_gts *gts, int gain, int time, int *scale_int, + int *scale_nano); +int iio_gts_find_new_gain_sel_by_old_gain_time(struct iio_gts *gts, + int old_gain, int old_time_sel, + int new_time_sel, int *new_gain); + +#endif From patchwork Wed Feb 22 16:15:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149335 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 59332C636D6 for ; Wed, 22 Feb 2023 16:15:19 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232243AbjBVQPS (ORCPT ); Wed, 22 Feb 2023 11:15:18 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35588 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232738AbjBVQPO (ORCPT ); Wed, 22 Feb 2023 11:15:14 -0500 Received: from mail-lf1-x12a.google.com (mail-lf1-x12a.google.com [IPv6:2a00:1450:4864:20::12a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8EB683D908; Wed, 22 Feb 2023 08:15:11 -0800 (PST) Received: by mail-lf1-x12a.google.com with SMTP id m7so10639537lfj.8; Wed, 22 Feb 2023 08:15:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=TZP+vTX8kZ9XZ4ezQH1LOcOUqExahRwe8VBSIcJ/Cp4=; b=Ga0Sx6kVr2RoKHNTK0J3TFrUfJ6LTCnas72I6Guw6oMMAb9U6XMkpBWrmw8CgOFMbw 2Pf7XMhnKzNUtkfAipWIP5y8bOgQhqpNh5yUw2L/DhJ+Ql93tsZ5WARmIN16OOiYARt4 pHPujbkc3ngAralwaWixQPNKv4Hg4u45yAuwkvZT0wq2Fi8db9JT0oBsyievMX3hx7PU vRyEqcpNZucSKP9du6vKxzO0vTjXLJGVHipsGiuwZh2a79eiwxXTdljIKUeSXXVh9Lt5 hP2x5U+wQcOaHCMSsrIOsiIv4jGsyyflCGLj/aRqXH7ypC5I7/HGpU8HBGadRlQQjyLv 3mhw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=TZP+vTX8kZ9XZ4ezQH1LOcOUqExahRwe8VBSIcJ/Cp4=; b=4NkaLw2zwC2+0KNDoq0RMpJ3qQ104HNpceZ+LGb2lRLPMOnrbYVPIhWDco03ZMyOV2 4iu22ASELng4dA+S2chJNmtYleyE2fPzCbgvFEV8jXJlbkgoLMkDHD3SMkfBoj1PN29m 83OIYJ23opgKxBGLtTjFc3uVU+6Kl61G7xRFQlWL8R/mt7PzfiT5EDkV3w3OlR9TuoFf fVOXxcjQ13RqoAFWVU6J/kY7ezxcybw8WiRGGtppttq61bAAdB6R/aOdek15ArpuU45/ 2bLyA6+IQLn8vp3xwiBhvnX60g4EOXO9yAPM6ccorXW50NS08r/TmSZY9hYRoSAkhcR0 jCbw== X-Gm-Message-State: AO0yUKXFu+TB/1zWLnVSjE4WBnBI6OfLdK4DzPxl5apGy/ajx9dq++0w /LWWZ+VYzR14UqzaPQ/s0z0= X-Google-Smtp-Source: AK7set/Sa+UALc40XgcOoAor4Ql2Cxfh+AGu6Lf1gRR17v4skFwZtYwtMxUL+H/8Y/BHZ5sHk9+U+w== X-Received: by 2002:ac2:51c6:0:b0:4db:3e39:e8e2 with SMTP id u6-20020ac251c6000000b004db3e39e8e2mr3012237lfm.68.1677082509771; Wed, 22 Feb 2023 08:15:09 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id w22-20020ac254b6000000b004cb14fa604csm181111lfk.262.2023.02.22.08.15.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:15:09 -0800 (PST) Date: Wed, 22 Feb 2023 18:15:04 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 3/6] iio: test: test gain-time-scale helpers Message-ID: <5e1fdcd95544d40432adbb4ecd380da97a7d93ea.1677080089.git.mazziesaccount@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org Some light sensors can adjust both the HW-gain and integration time. There are cases where adjusting the integration time has similar impact to the scale of the reported values as gain setting has. IIO users do typically expect to handle scale by a single writable 'scale' entry. Driver should then adjust the gain/time accordingly. It however is difficult for a driver to know whether it should change gain or integration time to meet the requested scale. Usually it is preferred to have longer integration time which usually improves accuracy, but there may be use-cases where long measurement times can be an issue. Thus it can be preferable to allow also changing the integration time - but mitigate the scale impact by also changing the gain underneath. Eg, if integration time change doubles the measured values, the driver can reduce the HW-gain to half. The theory of the computations of gain-time-scale is simple. However, some people (undersigned) got that implemented wrong for more than once. Hence some gain-time-scale helpers were introduced. Add some simple tests to verify the most hairy functions. Signed-off-by: Matti Vaittinen --- drivers/iio/test/Kconfig | 15 ++ drivers/iio/test/Makefile | 1 + drivers/iio/test/iio-test-gts.c | 331 ++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 drivers/iio/test/iio-test-gts.c diff --git a/drivers/iio/test/Kconfig b/drivers/iio/test/Kconfig index 0b6e4e278a2f..b57f1fc440e6 100644 --- a/drivers/iio/test/Kconfig +++ b/drivers/iio/test/Kconfig @@ -4,6 +4,19 @@ # # Keep in alphabetical order +config IIO_GTS_KUNIT_TEST + tristate "Test IIO formatting functions" if !KUNIT_ALL_TESTS + depends on KUNIT + select IIO_GTS_HELPER + default KUNIT_ALL_TESTS + help + build unit tests for the IIO light sensor gain-time-scale helpers. + + For more information on KUnit and unit tests in general, please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. Keep in alphabetical order + config IIO_RESCALE_KUNIT_TEST tristate "Test IIO rescale conversion functions" if !KUNIT_ALL_TESTS depends on KUNIT && IIO_RESCALE @@ -27,3 +40,5 @@ config IIO_FORMAT_KUNIT_TEST to the KUnit documentation in Documentation/dev-tools/kunit/. If unsure, say N. + + diff --git a/drivers/iio/test/Makefile b/drivers/iio/test/Makefile index d76eaf36da82..e9a4cf1ff57f 100644 --- a/drivers/iio/test/Makefile +++ b/drivers/iio/test/Makefile @@ -6,4 +6,5 @@ # Keep in alphabetical order obj-$(CONFIG_IIO_RESCALE_KUNIT_TEST) += iio-test-rescale.o obj-$(CONFIG_IIO_FORMAT_KUNIT_TEST) += iio-test-format.o +obj-$(CONFIG_IIO_GTS_KUNIT_TEST) += iio-test-gts.o CFLAGS_iio-test-format.o += $(DISABLE_STRUCTLEAK_PLUGIN) diff --git a/drivers/iio/test/iio-test-gts.c b/drivers/iio/test/iio-test-gts.c new file mode 100644 index 000000000000..0096dd19e009 --- /dev/null +++ b/drivers/iio/test/iio-test-gts.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Unit tests for IIO light sensor gain-time-scale helpers + * + * Copyright (c) 2023 Matti Vaittinen + */ + +#include +#include "../light/gain-time-scale-helper.h" + +/* + * Please, read the "rant" from the top of the lib/test_linear_ranges.c if + * you see a line of helper code which is not being tested. + * + * Then, please look at the line which is not being tested. Is this line + * somehow unusually complex? If answer is "no", then chances are that the + * "development inertia" caused by adding a test exceeds the benefits. + * + * If yes, then adding a test is probably a good idea but please stop for a + * moment and consider the effort of changing all the tests when code gets + * refactored. Eventually it neeeds to be. + */ + +#define TEST_TSEL_50 1 +#define TEST_TSEL_X_MIN TEST_TSEL_50 +#define TEST_TSEL_100 0 +#define TEST_TSEL_200 2 +#define TEST_TSEL_400 4 +#define TEST_TSEL_X_MAX TEST_TSEL_400 + +#define TEST_GSEL_1 0x00 +#define TEST_GSEL_X_MIN TEST_GSEL_1 +#define TEST_GSEL_4 0x08 +#define TEST_GSEL_16 0x0a +#define TEST_GSEL_32 0x0b +#define TEST_GSEL_64 0x0c +#define TEST_GSEL_256 0x18 +#define TEST_GSEL_512 0x19 +#define TEST_GSEL_1024 0x1a +#define TEST_GSEL_2048 0x1b +#define TEST_GSEL_4096 0x1c +#define TEST_GSEL_X_MAX TEST_GSEL_4096 + +#define TEST_SCALE_1X 64 +#define TEST_SCALE_MIN_X TEST_SCALE_1X +#define TEST_SCALE_2X 32 +#define TEST_SCALE_4X 16 +#define TEST_SCALE_8X 8 +#define TEST_SCALE_16X 4 +#define TEST_SCALE_32X 2 +#define TEST_SCALE_64X 1 + +#define TEST_SCALE_NANO_128X 500000000 +#define TEST_SCALE_NANO_256X 250000000 +#define TEST_SCALE_NANO_512X 125000000 +#define TEST_SCALE_NANO_1024X 62500000 +#define TEST_SCALE_NANO_2048X 31250000 +#define TEST_SCALE_NANO_4096X 15625000 +#define TEST_SCALE_NANO_4096X2 7812500 +#define TEST_SCALE_NANO_4096X4 3906250 +#define TEST_SCALE_NANO_4096X8 1953125 + +#define TEST_SCALE_NANO_MAX_X TEST_SCALE_NANO_4096X8 + +static const struct iio_gain_sel_pair gts_test_gains[] = { + GAIN_SCALE_GAIN(1, TEST_GSEL_1), + GAIN_SCALE_GAIN(4, TEST_GSEL_4), + GAIN_SCALE_GAIN(16, TEST_GSEL_16), + GAIN_SCALE_GAIN(32, TEST_GSEL_32), + GAIN_SCALE_GAIN(64, TEST_GSEL_64), + GAIN_SCALE_GAIN(256, TEST_GSEL_256), + GAIN_SCALE_GAIN(512, TEST_GSEL_512), + GAIN_SCALE_GAIN(1024, TEST_GSEL_1024), + GAIN_SCALE_GAIN(2048, TEST_GSEL_2048), + GAIN_SCALE_GAIN(4096, TEST_GSEL_4096), +#define HWGAIN_MAX 4096 +}; + +static const struct iio_itime_sel_mul gts_test_itimes[] = { + GAIN_SCALE_ITIME_MS(400, TEST_TSEL_400, 8), + GAIN_SCALE_ITIME_MS(200, TEST_TSEL_200, 4), + GAIN_SCALE_ITIME_MS(100, TEST_TSEL_100, 2), + GAIN_SCALE_ITIME_MS(50, TEST_TSEL_50, 1), +#define TIMEGAIN_MAX 8 +}; +#define TOTAL_GAIN_MAX (HWGAIN_MAX * TIMEGAIN_MAX) + +static int test_init_iio_gain_scale(struct iio_gts *gts, int max_scale_int, + int max_scale_nano) +{ + int ret; + + ret = iio_init_iio_gts(max_scale_int, max_scale_nano, gts_test_gains, + ARRAY_SIZE(gts_test_gains), gts_test_itimes, + ARRAY_SIZE(gts_test_itimes), gts); + + return ret; +} + +static void test_iio_gts_find_gain_for_scale_using_time(struct kunit *test) +{ + struct iio_gts gts; + int ret, gain; + + ret = test_init_iio_gain_scale(>s, TEST_SCALE_1X, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_100, + TEST_SCALE_8X, 0, &gain); + /* + * Meas time 100 => gain by time 2x + * TEST_SCALE_8X matches total gain 8x + * => required HWGAIN 4x + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, 4, gain); + + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_200, 0, + TEST_SCALE_NANO_256X, &gain); + /* + * Meas time 200 => gain by time 4x + * TEST_SCALE_256X matches total gain 256x + * => required HWGAIN 256/4 => 64x + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, 64, gain); + + /* Min time, Min gain */ + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_X_MIN, + TEST_SCALE_MIN_X, 0, &gain); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, 1, gain); + + /* Max time, Max gain */ + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_X_MAX, 0, + TEST_SCALE_NANO_MAX_X, &gain); + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, 4096, gain); + + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_100, 0, + TEST_SCALE_NANO_256X, &gain); + /* + * Meas time 100 => gain by time 2x + * TEST_SCALE_256X matches total gain 256x + * => required HWGAIN 256/2 => 128x (not in gain-table - unsupported) + */ + KUNIT_EXPECT_NE(test, 0, ret); + + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_200, 0, + TEST_SCALE_NANO_MAX_X, &gain); + /* We can't reach the max gain with integration time smaller than MAX */ + KUNIT_EXPECT_NE(test, 0, ret); + + ret = iio_gts_find_gain_for_scale_using_time(>s, TEST_TSEL_50, 0, + TEST_SCALE_NANO_MAX_X, &gain); + /* We can't reach the max gain with integration time smaller than MAX */ + KUNIT_EXPECT_NE(test, 0, ret); +} + +static void test_iio_gts_find_time_and_gain_sel_for_scale(struct kunit *test) +{ + struct iio_gts gts; + int ret, gain_sel, time_sel; + + ret = test_init_iio_gain_scale(>s, TEST_SCALE_1X, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + + ret = iio_gts_find_time_and_gain_sel_for_scale(>s, 0, + TEST_SCALE_NANO_256X, &gain_sel, &time_sel); + /* + * We should find time 400 (8x) and gain 256/8 => 32x because the + * time 400 is listed first + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_32, gain_sel); + KUNIT_EXPECT_EQ(test, TEST_TSEL_400, time_sel); + + ret = iio_gts_find_time_and_gain_sel_for_scale(>s, TEST_SCALE_64X, + 0, &gain_sel, &time_sel); + /* + * We should find time 200 (4x) and gain 64/4 => 16x. The most + * preferred time 400 (8x) would require gain 8x - which is not + * "supported". + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_16, gain_sel); + KUNIT_EXPECT_EQ(test, TEST_TSEL_200, time_sel); + + /* Min gain */ + ret = iio_gts_find_time_and_gain_sel_for_scale(>s, TEST_SCALE_MIN_X, + 0, &gain_sel, &time_sel); + /* + * We should find time 400 (8x) and gain 256/8 => 32x because the + * time 400 is listed first + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_1, gain_sel); + KUNIT_EXPECT_EQ(test, TEST_TSEL_50, time_sel); + + /* Max gain */ + ret = iio_gts_find_time_and_gain_sel_for_scale(>s, 0, + TEST_SCALE_NANO_MAX_X, &gain_sel, &time_sel); + /* + * We should find time 400 (8x) and gain 256/8 => 32x because the + * time 400 is listed first + */ + KUNIT_EXPECT_EQ(test, 0, ret); + KUNIT_EXPECT_EQ(test, TEST_GSEL_X_MAX, gain_sel); + KUNIT_EXPECT_EQ(test, TEST_TSEL_X_MAX, time_sel); +} + +static void test_iio_gts_get_total_gain_by_sel(struct kunit *test) +{ + struct iio_gts gts; + int ret, gain_sel, time_sel; + + ret = test_init_iio_gain_scale(>s, TEST_SCALE_1X, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + + /* gain x32, time x4 => total gain 32 * 4 = 128 */ + gain_sel = TEST_GSEL_32; + time_sel = TEST_TSEL_200; + + ret = iio_gts_get_total_gain_by_sel(>s, gain_sel, time_sel); + /* gain x32, time x8 => total gain 32 * 4 = 128 */ + KUNIT_EXPECT_EQ(test, 128, ret); + + gain_sel = TEST_GSEL_X_MAX; + time_sel = TEST_TSEL_X_MAX; + ret = iio_gts_get_total_gain_by_sel(>s, gain_sel, time_sel); + KUNIT_EXPECT_EQ(test, TOTAL_GAIN_MAX, ret); + + gain_sel = TEST_GSEL_X_MIN; + time_sel = TEST_TSEL_X_MIN; + ret = iio_gts_get_total_gain_by_sel(>s, gain_sel, time_sel); + KUNIT_EXPECT_EQ(test, 1, ret); +} + +static void test_iio_gts_find_new_gain_sel_by_old_gain_time(struct kunit *test) +{ + struct iio_gts gts; + int ret, old_gain, new_gain, old_time_sel, new_time_sel; + + ret = test_init_iio_gain_scale(>s, TEST_SCALE_1X, 0); + KUNIT_EXPECT_EQ(test, 0, ret); + if (ret) + return; + + old_gain = 32; + old_time_sel = TEST_TSEL_200; + new_time_sel = TEST_TSEL_400; + + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * Doubling the integration time doubles the total gain - so old + * (hw)gain must be divided by two to compensate. => 32 / 2 => 16 + */ + KUNIT_EXPECT_EQ(test, 16, new_gain); + + old_gain = 4; + old_time_sel = TEST_TSEL_50; + new_time_sel = TEST_TSEL_200; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * gain by time 1x => 4x - (hw)gain 4x => 1x + */ + KUNIT_EXPECT_EQ(test, 1, new_gain); + + old_gain = 512; + old_time_sel = TEST_TSEL_400; + new_time_sel = TEST_TSEL_50; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_EQ(test, 0, ret); + /* + * gain by time 8x => 1x - (hw)gain 512x => 4096x) + */ + KUNIT_EXPECT_EQ(test, 4096, new_gain); + + /* Unsupported gain 2x */ + old_gain = 4; + old_time_sel = TEST_TSEL_200; + new_time_sel = TEST_TSEL_400; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); + + /* Too small gain */ + old_gain = 4; + old_time_sel = TEST_TSEL_50; + new_time_sel = TEST_TSEL_400; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); + + /* Too big gain */ + old_gain = 1024; + old_time_sel = TEST_TSEL_400; + new_time_sel = TEST_TSEL_50; + ret = iio_gts_find_new_gain_sel_by_old_gain_time(>s, old_gain, + old_time_sel, new_time_sel, &new_gain); + KUNIT_EXPECT_NE(test, 0, ret); +} + +static struct kunit_case iio_gts_test_cases[] = { + KUNIT_CASE(test_iio_gts_find_gain_for_scale_using_time), + KUNIT_CASE(test_iio_gts_find_time_and_gain_sel_for_scale), + KUNIT_CASE(test_iio_gts_get_total_gain_by_sel), + KUNIT_CASE(test_iio_gts_find_new_gain_sel_by_old_gain_time), + {} +}; + +static struct kunit_suite iio_gts_test_suite = { + .name = "iio-gain-time-scale", + .test_cases = iio_gts_test_cases, +}; + +kunit_test_suite(iio_gts_test_suite); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("Test IIO light sensor gain-time-scale helpers"); From patchwork Wed Feb 22 16:15:34 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149336 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 16354C61DA4 for ; Wed, 22 Feb 2023 16:15:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232706AbjBVQP4 (ORCPT ); Wed, 22 Feb 2023 11:15:56 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36548 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231351AbjBVQPz (ORCPT ); Wed, 22 Feb 2023 11:15:55 -0500 Received: from mail-lj1-x22e.google.com (mail-lj1-x22e.google.com [IPv6:2a00:1450:4864:20::22e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 09AC83D0BE; Wed, 22 Feb 2023 08:15:43 -0800 (PST) Received: by mail-lj1-x22e.google.com with SMTP id e24so2122681ljj.3; Wed, 22 Feb 2023 08:15:42 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=xz3lK4uXKSeVg8DcNZTFoTaIwiKybc6jHkOnoRlG2ME=; b=MxsTDX4EQg80bndxgGa0zhgfWuvhUzJ9qqC2GFJ79xKwk4nGd9FS1lvpnnXubMbNY8 FLRLid1tZtemjFiNb7SRPBmllQ/0NgInVn8WealcvKXMj86/qHS/P28YuaRVAh6QIw7B 0mxE8ZPVxabBA0kLSIWCmYcTv3fqd//o/cJH3OVPRJk99TY4ME7yjTYg1lHa0wNAXR/H EjDS2x1Q7FnZdYtMyRRECn4trPbWsGQ3PsBYncOJGmki9ksSX89CS3w+hCNVvSrvw9cw xAT7NzYxkP0TzAOLIMn1/NZBVrWWwCc+yJWX2o7Db5QiAD7QzMkDXDT1DD+03NcvoG9F z0ew== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=xz3lK4uXKSeVg8DcNZTFoTaIwiKybc6jHkOnoRlG2ME=; b=luvkziuAVKhs4Cc7DX9txkOLOwCG/MieuF1GfESPMP/vBz8kjcsqkWKLnAD9SIk2wY 5ApdY1d3+66AHr5AOdfN7kcsVGucxJP7JkFcvSyUsnBEy1w2u3AlpwfKUSo3GFTBRk1B cOu7mfgBTHL7hzUOxBabKZWnR5k08c2SyeQFOEGEwe0Qgq38Rn5DaH/cla8olNUImIyS zWTN1WzxaxMSP+afaNuufiM1MmVOospSCM8UnI7e8Vo1NfGmg8+SC6LsPu+NGBP6dGKf IuHa/Lq35tIdhSkFdoSArCWpoDXQRkQm+k2N657y1WtGNNK4x5zRxaH3BybPPGOGNATI HjFw== X-Gm-Message-State: AO0yUKVcM69Sb5A9Q+Il71w9JI+iuD9+qz1Qi7ZuwlsFhweP3+TIeFMy 0EOWkG05zZS3M1LocylxVos= X-Google-Smtp-Source: AK7set+nGDjPROPW++aKYnxhiYlswuO95tomifQi+o+Rfc1x8NgNNeyCm5vljnmCXKdbpwBsgJtvsQ== X-Received: by 2002:a2e:9152:0:b0:295:9970:9f73 with SMTP id q18-20020a2e9152000000b0029599709f73mr988894ljg.29.1677082541240; Wed, 22 Feb 2023 08:15:41 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id bi37-20020a05651c232500b0028b6e922ba1sm1183658ljb.30.2023.02.22.08.15.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:15:40 -0800 (PST) Date: Wed, 22 Feb 2023 18:15:34 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 4/6] MAINTAINERS: Add IIO gain-time-scale helpers Message-ID: References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org Add myself as a maintainer for IIO light sensor helpers (helpers for maintaining the scale while adjusting intergration time or gain) and related Kunit tests. Signed-off-by: Matti Vaittinen --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 42fc47c6edfd..43f5a024daa2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10052,6 +10052,14 @@ F: Documentation/ABI/testing/sysfs-bus-iio-adc-envelope-detector F: Documentation/devicetree/bindings/iio/adc/envelope-detector.yaml F: drivers/iio/adc/envelope-detector.c +IIO LIGHT SENSOR GAIN-TIME-SCALE HELPERS +M: Matti Vaittinen +L: linux-iio@vger.kernel.org +S: Maintained +F: drivers/iio/light/gain-time-scale-helper.c +F: drivers/iio/light/gain-time-scale-helper.h +F: drivers/iio/test/iio-test-gts.c + IIO MULTIPLEXER M: Peter Rosin L: linux-iio@vger.kernel.org From patchwork Wed Feb 22 16:15:58 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149337 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4BE42C636D6 for ; Wed, 22 Feb 2023 16:16:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231351AbjBVQQO (ORCPT ); Wed, 22 Feb 2023 11:16:14 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37078 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232727AbjBVQQL (ORCPT ); Wed, 22 Feb 2023 11:16:11 -0500 Received: from mail-lf1-x12c.google.com (mail-lf1-x12c.google.com [IPv6:2a00:1450:4864:20::12c]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2A44D3D917; Wed, 22 Feb 2023 08:16:06 -0800 (PST) Received: by mail-lf1-x12c.google.com with SMTP id m7so10643322lfj.8; Wed, 22 Feb 2023 08:16:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=dqwusWq3rWXuEmRuQNIHBKPx2dAlMYU8G0Dt7tz4/e4=; b=pXQuuusE9kq51MPDx/8EMziaX22luOCKTgtco/T4hsNq8oQ18koRjp0/lKML+ys8oq jHiOPoZSQ+gS8yr3NKVE4ZSaRGF/JvD4EsscmkqCy4w960FArartGaTlQkfq/eWmpTog aNUW6wRNmBGGC+ujXDQ1vjZsZbdOKw0tnG6rvuHsDem+USaR932wCdjECj2rRDhn+NO8 PXFsThX3+mAo5ox+VaXxQKpY+9OHYMHJBnXgyw554/bDp5osPy6YcVJ52pduloxiaXoY exzbQZcbj60dIcZymSVgBMcg0LuusXCMhcYOx7odovwxZ9wG51v95L7q6tnjVGzJr1Sf 8Kww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=dqwusWq3rWXuEmRuQNIHBKPx2dAlMYU8G0Dt7tz4/e4=; b=43RJv1lGryKYWmw/JtEDEUjy7fHR8JW4pMlDQUiFaXm4/Y6Kcmoc4zZIz7BLTlhgKO V2q9ZtxPAkWn9JfzmTgIQ5pND/OTtMtUyo2hjxEoi7CPD17g0GHmqN7XGPsoKnImswb1 +UEWAbzpvwTj4dS5KJVY91X5c4Rcn9hck8OF3LTnyhkHrJ9uXI9UAwDTUHcmpIm/DVYc /ttBp3BxmmRKomOUOplDVFH2hEWBoa/LRE8Z6S2AAF1xjFolSDcQBxmWoSayl4piOW0H bEOarehX3lI98uwL+ijDK/5ULdNQNHFLggEvZvUlgFUbgy8xlkFsBmFxoA9LpX3xrqNd dOOA== X-Gm-Message-State: AO0yUKVVYJ+TJIA2KyMytap0jN/hREctA9okMFYFnwoy0U6kLQAtVHEl BXmCDLTnCR7fLiz/rW6fmEM= X-Google-Smtp-Source: AK7set+s5BXwhzb3Rxp4a+Fwk9P8Y9T/pUDS7z+nrzFzvEHrcXaFJJRcccks9g9TX5xaGc+B011FKQ== X-Received: by 2002:ac2:44c7:0:b0:4db:297b:5cbe with SMTP id d7-20020ac244c7000000b004db297b5cbemr2551214lfm.34.1677082564252; Wed, 22 Feb 2023 08:16:04 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id w8-20020ac24428000000b004cafa2bfb7dsm1133125lfl.133.2023.02.22.08.16.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:16:03 -0800 (PST) Date: Wed, 22 Feb 2023 18:15:58 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 5/6] iio: light: ROHM BU27034 Ambient Light Sensor Message-ID: <63a2dbedf54e2e00e3b63dd16aae190ff6596355.1677080089.git.mazziesaccount@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org ROHM BU27034 is an ambient light sesnor with 3 channels and 3 photo diodes capable of detecting a very wide range of illuminance. Typical application is adjusting LCD and backlight power of TVs and mobile phones. Add initial support for the ROHM BU27034 ambient light sensor. NOTE: - Driver exposes 4 channels. One IIO_LIGHT channel providing the calculated lux values based on measured data from diodes #0 and #1. Additionally 3 IIO_INTENSITY channels are emitting the raw register data from all diodes for more intense user-space computations. - Sensor has adjustible GAIN values ranging from 1x to 4096x. - Sensor has adjustible measurement times 5, 55, 100, 200 and 400 mS. Driver does not support 5 mS which has special limitations. - Driver exposes standard 'scale' adjustment which is implemented by: 1) Trying to adjust only the GAIN 2) If GAIN adjustment only can't provide requested scale, adjusting both the time and the gain is attempted. - Driver exposes writable INT_TIME property which can be used for adjusting the measurement time. Time adjustment will also cause the driver to adjust the GAIN so that the overall scale is not changed. - Runtime PM is not implemented. - Driver starts the measurement on the background when it is probed. This improves the respnse time to read-requests compared to starting the read only when data is requested. When the most accurate 400 mS measurement time is used, data reads would last quite long if measurement was started only on demand. This, however, is not appealing for users who would prefere power saving over measurement response time. Signed-off-by: Matti Vaittinen --- --- drivers/iio/light/Kconfig | 13 + drivers/iio/light/Makefile | 1 + drivers/iio/light/rohm-bu27034.c | 1212 ++++++++++++++++++++++++++++++ 3 files changed, 1226 insertions(+) create mode 100644 drivers/iio/light/rohm-bu27034.c diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 671d84f98c56..594228bd1f7f 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -292,6 +292,19 @@ config JSA1212 To compile this driver as a module, choose M here: the module will be called jsa1212. +config ROHM_BU27034 + tristate "ROHM BU27034 ambient light sensor" + depends on I2C + select REGMAP_I2C + select IIO_GTS_HELPER + help + Enable support for the ROHM BU27034 ambient light sensor. + ROHM BU27034 is an ambient light sesnor with 3 channels and + 3 photo diodes capable of detecting a very wide range of + illuminance. + Typical application is adjusting LCD and backlight power + of TVs and mobile phones. + config RPR0521 tristate "ROHM RPR0521 ALS and proximity sensor driver" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index f4705fac7a96..d34a0f7bf6ce 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_MAX44009) += max44009.o obj-$(CONFIG_NOA1305) += noa1305.o obj-$(CONFIG_OPT3001) += opt3001.o obj-$(CONFIG_PA12203001) += pa12203001.o +obj-$(CONFIG_ROHM_BU27034) += rohm-bu27034.o obj-$(CONFIG_RPR0521) += rpr0521.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_SI1133) += si1133.o diff --git a/drivers/iio/light/rohm-bu27034.c b/drivers/iio/light/rohm-bu27034.c new file mode 100644 index 000000000000..235be7dee6e0 --- /dev/null +++ b/drivers/iio/light/rohm-bu27034.c @@ -0,0 +1,1212 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BU27034 ROHM Ambient Light Sensor + * + * Copyright (c) 2023, ROHM Semiconductor. + * https://fscdn.rohm.com/en/products/databook/datasheet/ic/sensor/light/bu27034nuc-e.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gain-time-scale-helper.h" + +#define BU27034_REG_SYSTEM_CONTROL 0x40 +#define BU27034_MASK_SW_RESET BIT(7) +#define BU27034_MASK_PART_ID GENMASK(5, 0) +#define BU27034_ID 0x19 +#define BU27034_REG_MODE_CONTROL1 0x41 +#define BU27034_MASK_MEAS_MODE GENMASK(2, 0) + +#define BU27034_REG_MODE_CONTROL2 0x42 +#define BU27034_MASK_D01_GAIN GENMASK(7, 3) +#define BU27034_SHIFT_D01_GAIN 3 +#define BU27034_MASK_D2_GAIN_HI GENMASK(7, 6) +#define BU27034_MASK_D2_GAIN_LO GENMASK(2, 0) +#define BU27034_SHIFT_D2_GAIN 3 + +#define BU27034_REG_MODE_CONTROL3 0x43 +#define BU27034_REG_MODE_CONTROL4 0x44 +#define BU27034_MASK_MEAS_EN BIT(0) +#define BU27034_MASK_VALID BIT(7) +#define BU27034_REG_DATA0_LO 0x50 +#define BU27034_REG_DATA1_LO 0x52 +#define BU27034_REG_DATA2_LO 0x54 +#define BU27034_REG_DATA2_HI 0x55 +#define BU27034_REG_MANUFACTURER_ID 0x92 +#define BU27034_REG_MAX BU27034_REG_MANUFACTURER_ID + +enum { + BU27034_CHAN_ALS, + BU27034_CHAN_DATA0, + BU27034_CHAN_DATA1, + BU27034_CHAN_DATA2, + BU27034_NUM_CHANS +}; + +/* + * Available scales with gain 1x - 4096x, timings 55, 100, 200, 400 mS + * Time impacts to gain: 1x, 2x, 4x, 8x. + * + * => Max total gain is HWGAIN * gain by integration time (8 * 4096) = 32768 + * + * Using NANO precision for scale we must use scale 64x corresponding gain 1x + * to avoid precision loss. (32x would result scale 976 562.5(nanos). + */ +#define BU27034_SCALE_1X 64 + +#define BU27034_GSEL_1X 0x00 +#define BU27034_GSEL_4X 0x08 +#define BU27034_GSEL_16X 0x0a +#define BU27034_GSEL_32X 0x0b +#define BU27034_GSEL_64X 0x0c +#define BU27034_GSEL_256X 0x18 +#define BU27034_GSEL_512X 0x19 +#define BU27034_GSEL_1024X 0x1a +#define BU27034_GSEL_2048X 0x1b +#define BU27034_GSEL_4096X 0x1c + +/* Available gain settings */ +static const struct iio_gain_sel_pair bu27034_gains[] = { + GAIN_SCALE_GAIN(1, BU27034_GSEL_1X), + GAIN_SCALE_GAIN(4, BU27034_GSEL_4X), + GAIN_SCALE_GAIN(16, BU27034_GSEL_16X), + GAIN_SCALE_GAIN(32, BU27034_GSEL_32X), + GAIN_SCALE_GAIN(64, BU27034_GSEL_64X), + GAIN_SCALE_GAIN(256, BU27034_GSEL_256X), + GAIN_SCALE_GAIN(512, BU27034_GSEL_512X), + GAIN_SCALE_GAIN(1024, BU27034_GSEL_1024X), + GAIN_SCALE_GAIN(2048, BU27034_GSEL_2048X), + GAIN_SCALE_GAIN(4096, BU27034_GSEL_4096X), +}; + +/* + * The IC has 5 modes for sampling time. 5 mS mode is exceptional as it limits + * the data collection to data0-channel only and cuts the supported range to + * 10 bit. It is not aupported by the driver. + * + * "normal" modes are 55, 100, 200 and 400 mS modes - which do have direct + * multiplying impact to the register values similar to gain. + * + * This means that if meas-mode is changed for example from 400 => 200, + * the scale is doubled. Eg, time impact to total gain is x1, x2, x4, x8. + */ +#define BU27034_MEAS_MODE_100MS 0 +#define BU27034_MEAS_MODE_55MS 1 +#define BU27034_MEAS_MODE_200MS 2 +#define BU27034_MEAS_MODE_400MS 4 + +static const struct iio_itime_sel_mul bu27034_itimes[] = { + GAIN_SCALE_ITIME_MS(400, BU27034_MEAS_MODE_400MS, 8), + GAIN_SCALE_ITIME_MS(200, BU27034_MEAS_MODE_200MS, 4), + GAIN_SCALE_ITIME_MS(100, BU27034_MEAS_MODE_100MS, 2), + GAIN_SCALE_ITIME_MS(50, BU27034_MEAS_MODE_55MS, 1), +}; + +#define BU27034_CHAN_DATA(_name, _ch2) \ +{ \ + .type = IIO_INTENSITY, \ + .channel = BU27034_CHAN_##_name, \ + .channel2 = (_ch2), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_INT_TIME), \ + .address = BU27034_REG_##_name##_LO, \ + .scan_index = BU27034_CHAN_##_name, \ + .scan_type = { \ + .sign = 'u', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .indexed = 1 \ +} + +static const struct iio_chan_spec bu27034_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .channel = BU27034_CHAN_ALS, + }, + BU27034_CHAN_DATA(DATA0, IIO_MOD_LIGHT_CLEAR), + BU27034_CHAN_DATA(DATA1, IIO_MOD_LIGHT_CLEAR), + BU27034_CHAN_DATA(DATA2, IIO_MOD_LIGHT_IR), +}; + +struct bu27034_data { + struct regmap *regmap; + struct device *dev; + /* + * Protect gain and time during scale adjustment and data reading as + * well as the channel data 'cached' flag. + */ + struct mutex mutex; + struct iio_gts gts; + bool cached; + __le16 raw[3]; +}; + +struct bu27034_result { + u16 ch0; + u16 ch1; + u16 ch2; +}; + +static const struct regmap_range bu27034_volatile_ranges[] = { + { + .range_min = BU27034_REG_MODE_CONTROL4, + .range_max = BU27034_REG_MODE_CONTROL4, + }, { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA2_HI, + }, +}; + +static const struct regmap_access_table bu27034_volatile_regs = { + .yes_ranges = &bu27034_volatile_ranges[0], + .n_yes_ranges = ARRAY_SIZE(bu27034_volatile_ranges), +}; + +static const struct regmap_range bu27034_read_only_ranges[] = { + { + .range_min = BU27034_REG_DATA0_LO, + .range_max = BU27034_REG_DATA2_HI, + }, { + .range_min = BU27034_REG_MANUFACTURER_ID, + .range_max = BU27034_REG_MANUFACTURER_ID, + } +}; + +static const struct regmap_access_table bu27034_ro_regs = { + .no_ranges = &bu27034_read_only_ranges[0], + .n_no_ranges = ARRAY_SIZE(bu27034_read_only_ranges), +}; + +static const struct regmap_config bu27034_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BU27034_REG_MAX, + .cache_type = REGCACHE_RBTREE, + .volatile_table = &bu27034_volatile_regs, +}; + +static int bu27034_validate_int_time(struct bu27034_data *data, int time_us) +{ + /* + * The BU27034 has 55 mS integration time which is in the vendor tests + * handled as 50 mS in all of the internal computations. We keep same + * approach here. + */ + if (time_us == 55000) + return 50000; + + if (iio_gts_valid_time(&data->gts, time_us)) + return time_us; + + return -EINVAL; +} + +struct bu27034_gain_check { + int old_gain; + int new_gain; + int chan; +}; + +static int bu27034_get_gain_sel(struct bu27034_data *data, int chan) +{ + int ret, val; + + switch (chan) { + case BU27034_CHAN_DATA0: + case BU27034_CHAN_DATA1: + { + int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + }; + ret = regmap_read(data->regmap, reg[chan], &val); + if (ret) + return ret; + + val &= BU27034_MASK_D01_GAIN; + return val >> BU27034_SHIFT_D01_GAIN; + } + case BU27034_CHAN_DATA2: + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL2, &val); + if (ret) + return ret; + + return (val & BU27034_MASK_D2_GAIN_HI) >> BU27034_SHIFT_D2_GAIN + | (val & BU27034_MASK_D2_GAIN_LO); + } + + dev_err(data->dev, "Can't get gain for channel %d\n", chan); + + return -EINVAL; +} + +static int bu27034_get_gain(struct bu27034_data *data, int chan, int *gain) +{ + int ret, sel; + + ret = bu27034_get_gain_sel(data, chan); + if (ret < 0) + return ret; + + sel = ret; + + ret = iio_gts_find_gain_by_sel(&data->gts, sel); + if (ret < 0) + dev_err(data->dev, "chan %u: unknown gain value 0x%x\n", chan, + sel); + + *gain = ret; + + return 0; +} + +static int bu27034_get_int_time(struct bu27034_data *data) +{ + int ret, sel; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &sel); + if (ret) + return ret; + + return iio_gts_find_int_time_by_sel(&data->gts, + sel & BU27034_MASK_MEAS_MODE); +} + +static int _bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int gain, ret; + + ret = bu27034_get_gain(data, channel, &gain); + if (ret) + return ret; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + return iio_gts_get_scale(&data->gts, gain, ret, val, val2); +} + +static int bu27034_get_scale(struct bu27034_data *data, int channel, int *val, + int *val2) +{ + int ret; + + mutex_lock(&data->mutex); + ret = _bu27034_get_scale(data, channel, val, val2); + mutex_unlock(&data->mutex); + + return ret; +} + +/* Caller should hold the lock to protect data->cached */ +static int bu27034_write_gain_sel(struct bu27034_data *data, int chan, int sel) +{ + static const int reg[] = { + [BU27034_CHAN_DATA0] = BU27034_REG_MODE_CONTROL2, + [BU27034_CHAN_DATA1] = BU27034_REG_MODE_CONTROL3, + [BU27034_CHAN_DATA2] = BU27034_REG_MODE_CONTROL2 + }; + int mask; + + if (chan < BU27034_CHAN_DATA0 || chan > BU27034_CHAN_DATA2) + return -EINVAL; + + if (chan == BU27034_CHAN_DATA0 || chan == BU27034_CHAN_DATA1) { + sel <<= BU27034_SHIFT_D01_GAIN; + mask = BU27034_MASK_D01_GAIN; + } else { + /* + * We don't allow setting high bits for DATA2 gain because + * that impacts to DATA0 as well. + */ + mask = BU27034_MASK_D2_GAIN_LO; + } + + /* We are changing gain so we need to invalidate cached results. */ + data->cached = false; + + return regmap_update_bits(data->regmap, reg[chan], mask, sel); +} + +static int _bu27034_set_gain(struct bu27034_data *data, int chan, int gain) +{ + int ret, sel; + + ret = iio_gts_find_sel_by_gain(&data->gts, gain); + if (ret < 0) + return ret; + + return bu27034_write_gain_sel(data, chan, sel); +} + +/* Caller should hold the lock to protect data->cached */ +static int bu27034_set_int_time(struct bu27034_data *data, int time) +{ + int ret; + + ret = iio_gts_find_sel_by_int_time(&data->gts, time); + if (ret < 0) + return ret; + + /* We are changing int time so we need to invalidate cached results. */ + data->cached = false; + + return regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, ret); +} + +/* + * We try to change the time in such way that the scale is maintained for + * given channels by adjusting gain so that it compensates the time change. + */ +static int bu27034_try_set_int_time(struct bu27034_data *data, int time_us) +{ + int ret, int_time_old, int_time_new, i; + struct bu27034_gain_check gains[3] = { + { .chan = BU27034_CHAN_DATA0, }, + { .chan = BU27034_CHAN_DATA1, }, + { .chan = BU27034_CHAN_DATA2 } + }; + int numg = ARRAY_SIZE(gains); + + mutex_lock(&data->mutex); + ret = bu27034_get_int_time(data); + if (ret < 0) + goto unlock_out; + + int_time_old = ret; + + ret = bu27034_validate_int_time(data, time_us); + if (ret < 0) { + dev_err(data->dev, "Unsupported integration time %u\n", + time_us); + + goto unlock_out; + } + + int_time_new = ret; + + if (int_time_new == int_time_old) { + ret = 0; + goto unlock_out; + } + + for (i = 0; i < numg; i++) { + ret = bu27034_get_gain(data, gains[i].chan, + &gains[i].old_gain); + if (ret) + goto unlock_out; + + gains[i].new_gain = gains[i].old_gain * int_time_old / + int_time_new; + + if (!iio_gts_valid_gain(&data->gts, gains[i].new_gain)) { + int scale1, scale2; + + _bu27034_get_scale(data, gains[i].chan, &scale1, &scale2); + dev_err(data->dev, + "chan %u, can't support time %u with scale %u %u\n", + gains[i].chan, time_us, scale1, scale2); + + ret = -EINVAL; + goto unlock_out; + } + } + + /* + * The new integration time can be supported while keeping the scale of + * channels intact by tuning the gains. + */ + for (i = 0; i < numg; i++) { + ret = _bu27034_set_gain(data, gains[i].chan, gains[i].new_gain); + if (ret) + goto unlock_out; + } + + ret = bu27034_set_int_time(data, int_time_new); + +unlock_out: + mutex_unlock(&data->mutex); + + return ret; +} + +static int bu27034_set_scale(struct bu27034_data *data, int chan, + int val, int val2) +{ + int ret, time_sel, gain_sel, i; + bool found = false; + + mutex_lock(&data->mutex); + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL1, &time_sel); + if (ret) + goto unlock_out; + + ret = iio_gts_find_gain_sel_for_scale_using_time(&data->gts, time_sel, + val, val2 * 1000, &gain_sel); + if (ret) { + /* We need to maintain the scale for all channels */ + int new_time_sel; + struct bu27034_gain_check gains[2]; + + if (chan == BU27034_CHAN_DATA0) { + gains[0].chan = BU27034_CHAN_DATA1; + gains[1].chan = BU27034_CHAN_DATA2; + } else if (chan == BU27034_CHAN_DATA1) { + gains[0].chan = BU27034_CHAN_DATA0; + gains[1].chan = BU27034_CHAN_DATA2; + } else { + gains[0].chan = BU27034_CHAN_DATA0; + gains[1].chan = BU27034_CHAN_DATA1; + } + for (i = 0; i < 2; i++) { + ret = bu27034_get_gain(data, gains[i].chan, + &gains[i].old_gain); + if (ret) + goto unlock_out; + } + + for (i = 0; i < data->gts.num_itime; i++) { + new_time_sel = data->gts.itime_table[i].sel; + + if (new_time_sel == time_sel) + continue; + + ret = iio_gts_find_gain_sel_for_scale_using_time( + &data->gts, new_time_sel, val, val2 * 1000, + &gain_sel); + if (ret) + continue; + + ret = iio_gts_find_new_gain_sel_by_old_gain_time( + &data->gts, gains[0].old_gain, time_sel, + new_time_sel, &gains[0].new_gain); + if (ret) + continue; + + ret = iio_gts_find_new_gain_sel_by_old_gain_time( + &data->gts, gains[1].old_gain, time_sel, + new_time_sel, &gains[1].new_gain); + if (!ret) { + found = true; + break; + } + } + if (!found) { + dev_err(data->dev, + "Can't set scale maintaining other channels\n"); + ret = -EINVAL; + + goto unlock_out; + } + + for (i = 0; i < 2; i++) { + ret = _bu27034_set_gain(data, gains[0].chan, + gains[0].new_gain); + if (ret) + goto unlock_out; + + ret = _bu27034_set_gain(data, gains[1].chan, + gains[1].new_gain); + if (ret) + goto unlock_out; + } + + ret = regmap_update_bits(data->regmap, BU27034_REG_MODE_CONTROL1, + BU27034_MASK_MEAS_MODE, new_time_sel); + if (ret) + goto unlock_out; + } + + ret = bu27034_write_gain_sel(data, chan, gain_sel); +unlock_out: + mutex_unlock(&data->mutex); + + return ret; +} + +/* + * for (D1/D0 < 0.87): + * lx = 0.004521097 * D1 - 0.002663996 * D0 + + * 0.00012213 * D1 * D1 / D0 + * + * => 115.7400832 * ch1 / gain1 / mt - + * 68.1982976 * ch0 / gain0 / mt + + * 0.00012213 * 25600 * (ch1 / gain1 / mt) * 25600 * + * (ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt) + * + * A = 0.00012213 * 25600 * (ch1 /gain1 / mt) * 25600 * + * (ch1 /gain1 / mt) / (25600 * ch0 / gain0 / mt) + * => 0.00012213 * 25600 * (ch1 /gain1 / mt) * + * (ch1 /gain1 / mt) / (ch0 / gain0 / mt) + * => 0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) / + * (ch0 / gain0) + * => 0.00012213 * 25600 * (ch1 / gain1) * (ch1 /gain1 / mt) * + * gain0 / ch0 + * => 3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt /ch0 + * + * lx = (115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) / + * mt + A + * => (115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0) / + * mt + 3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / mt / + * ch0 + * + * => (115.7400832 * ch1 / gain1 - 68.1982976 * ch0 / gain0 + + * 3.126528 * ch1 * ch1 * gain0 / gain1 / gain1 / ch0) / + * mt + * + * For (0.87 <= D1/D0 < 1.00) + * lx = (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 0.87) * (0.385) + 1) + * => (0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 * + * 100 * ch1 / gain1 / mt) * ((D1/D0 - 0.87) * (0.385) + 1) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * ((D1/D0 - 0.87) * (0.385) + 1) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * (0.385 * D1/D0 - 0.66505) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * (0.385 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) - 0.66505) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * (9856 * ch1 / gain1 / mt / (25600 * ch0 / gain0 / mt) + 0.66505) + * => 13.118336 * ch1 / (gain1 * mt) + * + 22.66064768 * ch0 / (gain0 * mt) + * + 8931.90144 * ch1 * ch1 * gain0 / + * (25600 * ch0 * gain1 * gain1 * mt) + * + 0.602694912 * ch1 / (gain1 * mt) + * + * => [0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + * + 22.66064768 * ch0 / gain0 + * + 13.721030912 * ch1 / gain1 + * ] / mt + * + * For (D1/D0 >= 1.00) + * + * lx = (0.001331* D0 + 0.0000354 * D1) * ((D1/D0 – 2.0) * (-0.05) + 1) + * => (0.001331* D0 + 0.0000354 * D1) * (-0.05D1/D0 + 1.1) + * => (0.001331 * 256 * 100 * ch0 / gain0 / mt + 0.0000354 * 256 * + * 100 * ch1 / gain1 / mt) * (-0.05D1/D0 + 1.1) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * (-0.05 * 256 * 100 * ch1 / gain1 / mt / (256 * 100 * ch0 / gain0 / mt) + 1.1) + * => (34.0736 * ch0 / gain0 / mt + 0.90624 * ch1 / gain1 / mt) * + * (-1280 * ch1 / (gain1 * mt * 25600 * ch0 / gain0 / mt) + 1.1) + * => (34.0736 * ch0 * -1280 * ch1 * gain0 * mt /( gain0 * mt * gain1 * mt * 25600 * ch0) + * + 34.0736 * 1.1 * ch0 / (gain0 * mt) + * + 0.90624 * ch1 * -1280 * ch1 *gain0 * mt / (gain1 * mt *gain1 * mt * 25600 * ch0) + * + 1.1 * 0.90624 * ch1 / (gain1 * mt) + * => -43614.208 * ch1 / (gain1 * mt * 25600) + * + 37.48096 ch0 / (gain0 * mt) + * - 1159.9872 * ch1 * ch1 * gain0 / (gain1 * gain1 * mt * 25600 * ch0) + * + 0.996864 ch1 / (gain1 * mt) + * => [ + * - 0.045312 * ch1 * ch1 * gain0 / (gain1 * gain1 * ch0) + * - 0.706816 * ch1 / gain1 + * + 37.48096 ch0 /gain0 + * ] * mt + * + * + * So, the first case (D1/D0 < 0.87) can be computed to a form: + * + * lx = (3.126528 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * 115.7400832 * ch1 / gain1 + + * -68.1982976 * ch0 / gain0 + * / mt + * + * Second case (0.87 <= D1/D0 < 1.00) goes to form: + * + * => [0.3489024 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * 13.721030912 * ch1 / gain1 + + * 22.66064768 * ch0 / gain0 + * ] / mt + * + * Third case (D1/D0 >= 1.00) goes to form: + * => [-0.045312 * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * -0.706816 * ch1 / gain1 + + * 37.48096 ch0 /(gain0 + * ] / mt + * + * This can be unified to format: + * lx = [ + * A * ch1 * ch1 * gain0 / (ch0 * gain1 * gain1) + + * B * ch1 / gain1 + + * C * ch0 / gain0 + * ] / mt + * + * For case 1: + * A = 3.126528, + * B = 115.7400832 + * C = -68.1982976 + * + * For case 2: + * A = 0.3489024 + * B = 13.721030912 + * C = 22.66064768 + * + * For case 3: + * A = -0.045312 + * B = -0.706816 + * C = 37.48096 + */ + +struct bu27034_lx_coeff { + unsigned int A; + unsigned int B; + unsigned int C; + /* Indicate which of the coefficients above are negative */ + bool is_neg[3]; +}; + +static u64 bu27034_fixp_calc_t1(unsigned int coeff, unsigned int ch0, + unsigned int ch1, unsigned int gain0, + unsigned int gain1) +{ + unsigned int helper, tmp; + u64 helper64; + + /* + * Here we could overflow even the 64bit value. Hence we + * multiply with gain0 only after the divisions - even though + * it may result loss of accuracy + */ + helper64 = (u64)coeff * (u64)ch1 * (u64)ch1; /* * (u64)gain0 */ + helper = coeff * ch1 * ch1; /* * gain0*/ + tmp = helper * gain0; + + if (helper == helper64 && (tmp / gain0 == helper)) + return tmp / (gain1 * gain1) / ch0; + + helper = gain1 * gain1; + if (helper > ch0) { + do_div(helper64, helper); + /* + * multiplication with max gain may overflow + * if helper64 is greater than 0xFFFFFFFFFFFFF. + * + * If this is the case we divide first. + */ + if (helper64 < 0xFFFFFFFFFFFFFLLU) { + helper64 *= gain0; + do_div(helper64, ch0); + } else { + do_div(helper64, ch0); + helper64 *= gain0; + } + + return helper64; + } + + do_div(helper64, ch0); + /* Same overflow check here */ + if (helper64 < 0xFFFFFFFFFFFFFLLU) { + helper64 *= gain0; + do_div(helper64, helper); + } else { + do_div(helper64, helper); + helper64 *= gain0; + } + + return helper64; +} + +static u64 bu27034_fixp_calc_t23(unsigned int coeff, unsigned int ch, + unsigned int gain) +{ + unsigned int helper; + u64 helper64; + + helper64 = (u64)coeff * (u64)ch; + helper = coeff * ch; + + if (helper == helper64) + return helper / gain; + + do_div(helper64, gain); + + return helper64; +} + +static int bu27034_fixp_calc_lx(unsigned int ch0, unsigned int ch1, + unsigned int gain0, unsigned int gain1, + unsigned int meastime, int coeff_idx) +{ + static const struct bu27034_lx_coeff coeff[] = { + { + .A = 31265280, /* 3.126528 */ + .B = 1157400832, /*115.7400832 */ + .C = 681982976, /* -68.1982976 */ + .is_neg = {false, false, true}, + }, { + .A = 3489024, /* 0.3489024 */ + .B = 137210309, /* 13.721030912 */ + .C = 226606476, /* 22.66064768 */ + /* All terms positive */ + }, { + .A = 453120, /* -0.045312 */ + .B = 7068160, /* -0.706816 */ + .C = 374809600, /* 37.48096 */ + .is_neg = {true, true, false}, + } + }; + const struct bu27034_lx_coeff *c = &coeff[coeff_idx]; + u64 res = 0, terms[3]; + int i; + + if (coeff_idx >= ARRAY_SIZE(coeff)) + return -EINVAL; + + terms[0] = bu27034_fixp_calc_t1(c->A, ch0, ch1, gain0, gain1); + terms[1] = bu27034_fixp_calc_t23(c->B, ch1, gain1); + terms[2] = bu27034_fixp_calc_t23(c->C, ch0, gain0); + + /* First, add positive terms */ + for (i = 0; i < 3; i++) + if (!c->is_neg[i]) + res += terms[i]; + + /* No positive term => zero lux */ + if (!res) + return 0; + + /* Then, subtract negative terms (if any) */ + for (i = 0; i < 3; i++) + if (c->is_neg[i]) { + /* + * If the negative term is greater than positive - then + * the darknes has taken over and we are all doomed! Eh, + * I mean, then we can just return 0 lx and go out + */ + if (terms[i] >= res) + return 0; + + res -= terms[i]; + } + + meastime *= 10000; + do_div(res, meastime); + + return (int) res; +} + +static bool bu27034_has_valid_sample(struct bu27034_data *data) +{ + int ret, val; + + ret = regmap_read(data->regmap, BU27034_REG_MODE_CONTROL4, &val); + if (ret) + dev_err(data->dev, "Read failed %d\n", ret); + + return (val & BU27034_MASK_VALID); +} + +static void bu27034_invalidate_read_data(struct bu27034_data *data) +{ + bu27034_has_valid_sample(data); +} + +static int _bu27034_get_result(struct bu27034_data *data, u16 *res, bool lock) +{ + int ret = 0; + +retry: + if (lock) + mutex_lock(&data->mutex); + /* Get new value from sensor if data is ready - or use cached value */ + if (bu27034_has_valid_sample(data)) { + ret = regmap_bulk_read(data->regmap, BU27034_REG_DATA0_LO, + &data->raw[0], sizeof(data->raw)); + if (ret) + goto unlock_out; + + data->cached = true; + bu27034_invalidate_read_data(data); + } else if (unlikely(!data->cached)) { + /* No new data in sensor and no value cached. Wait and retry */ + if (lock) + mutex_unlock(&data->mutex); + msleep(25); + + goto retry; + } + res[0] = le16_to_cpu(data->raw[0]); + res[1] = le16_to_cpu(data->raw[1]); + res[2] = le16_to_cpu(data->raw[2]); + +unlock_out: + if (lock) + mutex_unlock(&data->mutex); + + return ret; +} + +static int bu27034_get_result_unlocked(struct bu27034_data *data, u16 *res) +{ + return _bu27034_get_result(data, res, false); +} + +static int bu27034_get_result(struct bu27034_data *data, u16 *res) +{ + return _bu27034_get_result(data, res, true); +} + +/* + * The formula given by vendor for computing luxes out of data0 and data1 + * (in open air) is as follows: + * + * Let's mark: + * D0 = data0/ch0_gain/meas_time_ms * 25600 + * D1 = data1/ch1_gain/meas_time_ms * 25600 + * + * Then: + * if (D1/D0 < 0.87) + * lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 3.45 + 1) + * else if (D1/D0 < 1) + * lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 0.87) * 0.385 + 1) + * else + * lx = (0.001331 * D0 + 0.0000354 * D1) * ((D1 / D0 - 2) * -0.05 + 1) + * + * we try implementing it here. Users who have for example some colored lens + * need to modify the calculation but I hope this gives a starting point for + * those working with such devices. + * + * The first case (D1/D0 < 0.87) can be computed to a form: + * lx = 0.004521097 * D1 - 0.002663996 * D0 + 0.00012213 * D1 * D1 / D0 + */ +static int bu27034_get_lux(struct bu27034_data *data, int *val) +{ + unsigned int gain0, gain1, meastime; + unsigned int d1_d0_ratio_scaled; + u16 res[3], ch0, ch1; + u64 helper64; + int ret; + + mutex_lock(&data->mutex); + ret = bu27034_get_result_unlocked(data, &res[0]); + if (ret) + goto unlock_out; + + /* Avoid div by zero */ + if (!res[0]) + ch0 = 1; + else + ch0 = res[0]; + + if (!res[1]) + ch1 = 1; + else + ch1 = res[1]; + + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA0, &gain0); + if (ret) + goto unlock_out; + + ret = bu27034_get_gain(data, BU27034_CHAN_DATA1, &gain1); + if (ret) + goto unlock_out; + + ret = bu27034_get_int_time(data); + if (ret < 0) + goto unlock_out; + + meastime = ret; + + mutex_unlock(&data->mutex); + + d1_d0_ratio_scaled = (unsigned int)ch1 * (unsigned int)gain0 * 100; + helper64 = (u64)ch1 * (u64)gain0 * 100LLU; + + if (helper64 != d1_d0_ratio_scaled) { + unsigned int div = (unsigned int)ch0 * gain1; + + do_div(helper64, div); + d1_d0_ratio_scaled = helper64; + } else { + d1_d0_ratio_scaled /= ch0 * gain1; + } + + if (d1_d0_ratio_scaled < 87) + *val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 0); + else if (d1_d0_ratio_scaled < 100) + *val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 1); + else + *val = bu27034_fixp_calc_lx(ch0, ch1, gain0, gain1, meastime, 2); + + return 0; + +unlock_out: + mutex_unlock(&data->mutex); + + return ret; +} + +static int bu27034_meas_set(struct bu27034_data *data, bool en) +{ + int ret; + + mutex_lock(&data->mutex); + if (en) + ret = regmap_set_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); + else + ret = regmap_clear_bits(data->regmap, BU27034_REG_MODE_CONTROL4, + BU27034_MASK_MEAS_EN); + mutex_unlock(&data->mutex); + + return ret; +} + +static int bu27034_meas_en(struct bu27034_data *data) +{ + return bu27034_meas_set(data, true); +} + +static int bu27034_meas_dis(struct bu27034_data *data) +{ + return bu27034_meas_set(data, false); +} + +static int bu27034_read_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + + ret = bu27034_get_int_time(data); + if (ret < 0) + return ret; + + /* + * We use 50000 uS internally for all calculations and only + * convert it to 55000 before returning it to the user. + * + * This is becaise data-sheet says the time is 55 mS - but + * vendor provided computations used 50 mS. + */ + if (ret == 50000) + ret = 55000; + + *val2 = 0; + *val = ret; + + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_SCALE: + ret = bu27034_get_scale(data, chan->channel, val, val2); + if (ret) + return ret; + + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_RAW: + { + u16 res[3]; + + if (chan->type != IIO_INTENSITY) + return -EINVAL; + + if (chan->channel < BU27034_CHAN_DATA0 || + chan->channel > BU27034_CHAN_DATA2) + return -EINVAL; + /* + * Reading one channel at a time is inefficient. + * + * Hence we run the measurement on the background and always + * read all the channels. There are following caveats: + * 1) The VALID bit handling is racy. Valid bit clearing is not + * tied to reading the data in the hardware. We clear the + * valid-bit manually _after_ we have read the data - but this + * means there is a small time-window where new result may + * arrive between read and clear. This means we can miss a + * sample. For normal use this should not be fatal because + * usually the light is changing slowly. There might be + * use-cases for measuring more rapidly changing light but this + * driver is unsuitable for those cases anyways. (Smallest + * measurement time we support is 55 mS.) + * 2) Data readings more frequent than the meas_time will return + * the same cached values. This should not be a problem for the + * very same reason 1) is not a problem. + */ + ret = bu27034_get_result(data, &res[0]); + if (ret) + return ret; + + *val = res[chan->channel - BU27034_CHAN_DATA0]; + + return IIO_VAL_INT; + } + + case IIO_CHAN_INFO_PROCESSED: + if (chan->type != IIO_LIGHT) + return -EINVAL; + + ret = bu27034_get_lux(data, val); + if (ret) + return ret; + return IIO_VAL_INT; + + } + + return -EINVAL; +} + +static int bu27034_write_raw(struct iio_dev *idev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct bu27034_data *data = iio_priv(idev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return bu27034_set_scale(data, chan->channel, val, val2); + case IIO_CHAN_INFO_INT_TIME: + return bu27034_try_set_int_time(data, val); + } + + return -EINVAL; +} + +static const struct iio_info bu27034_info = { + .read_raw = &bu27034_read_raw, + .write_raw = &bu27034_write_raw, +}; + +static void bu27034_meas_stop(void *data) +{ + bu27034_meas_dis(data); +} + +static int bu27034_chip_init(struct bu27034_data *data) +{ + int ret; + + /* Reset */ + ret = regmap_update_bits(data->regmap, BU27034_REG_SYSTEM_CONTROL, + BU27034_MASK_SW_RESET, BU27034_MASK_SW_RESET); + if (ret) + return dev_err_probe(data->dev, ret, "Sensor reset failed\n"); + + /* + * Delay to allow IC to initialize. We don't care if we delay + * for more than 1 ms so msleep() is Ok. We just don't want to + * block + */ + msleep(1); + + /* + * Consider disabling the measurement (and powering off the sensor) for + * runtime pm + */ + ret = bu27034_meas_en(data); + if (ret) + return ret; + + return devm_add_action_or_reset(data->dev, bu27034_meas_stop, data); +} + +static int bu27034_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct fwnode_handle *fwnode; + struct bu27034_data *data; + struct regmap *regmap; + struct iio_dev *idev; + unsigned int part_id; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &bu27034_regmap); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize Regmap\n"); + + fwnode = dev_fwnode(dev); + if (!fwnode) + return -ENODEV; + + idev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!idev) + return -ENOMEM; + + ret = devm_regulator_get_enable_optional(dev, "vdd"); + if (ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get regulator\n"); + + data = iio_priv(idev); + + ret = regmap_read(regmap, BU27034_REG_SYSTEM_CONTROL, &part_id); + if (ret) + return dev_err_probe(dev, ret, "Failed to access sensor\n"); + + part_id &= BU27034_MASK_PART_ID; + + if (part_id != BU27034_ID) { + dev_err(dev, "unsupported device 0x%x\n", part_id); + return -EINVAL; + } + + ret = iio_init_iio_gts(BU27034_SCALE_1X, 0, bu27034_gains, + ARRAY_SIZE(bu27034_gains), bu27034_itimes, + ARRAY_SIZE(bu27034_itimes), &data->gts); + if (ret) + return ret; + + mutex_init(&data->mutex); + data->regmap = regmap; + data->dev = dev; + + idev->channels = bu27034_channels; + idev->num_channels = ARRAY_SIZE(bu27034_channels); + idev->name = "bu27034-als"; + idev->info = &bu27034_info; + + idev->modes = INDIO_DIRECT_MODE; + + ret = bu27034_chip_init(data); + if (ret) + return ret; + + ret = devm_iio_device_register(data->dev, idev); + if (ret < 0) + return dev_err_probe(dev, ret, + "Unable to register iio device\n"); + + return ret; +} + +static const struct of_device_id bu27034_of_match[] = { + { .compatible = "rohm,bu27034", }, + { } +}; +MODULE_DEVICE_TABLE(of, bu27034_of_match); + +static struct i2c_driver bu27034_i2c_driver = { + .driver = { + .name = "bu27034-i2c", + .of_match_table = bu27034_of_match, + }, + .probe_new = bu27034_probe, +}; +module_i2c_driver(bu27034_i2c_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matti Vaittinen "); +MODULE_DESCRIPTION("ROHM BU27034 ambient light sensor driver"); From patchwork Wed Feb 22 16:16:18 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matti Vaittinen X-Patchwork-Id: 13149338 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3C86EC636D6 for ; Wed, 22 Feb 2023 16:16:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S232727AbjBVQQc (ORCPT ); Wed, 22 Feb 2023 11:16:32 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37642 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232734AbjBVQQb (ORCPT ); Wed, 22 Feb 2023 11:16:31 -0500 Received: from mail-lf1-x12f.google.com (mail-lf1-x12f.google.com [IPv6:2a00:1450:4864:20::12f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 452C13D925; Wed, 22 Feb 2023 08:16:25 -0800 (PST) Received: by mail-lf1-x12f.google.com with SMTP id x24so10840941lfr.1; Wed, 22 Feb 2023 08:16:25 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=B9YMLBYcPz/4Hlblq1d7hFRHt4s8YknOklPReJ3GiyY=; b=b1xBoyvL2h5smGjmn94Ham0JSyYLz8r2UnWaGNSQONcCY9PuAzhG/nJE8V1/+kg5em 97DVyp+687d7Ig4+iHDslu9bnDBr9q1UoBDHGnf6N63BRuEI1t9o831id2QmSwHYwDfr 7bAkslkNRa1vtIaKIHqm84MR0sl7uxH48LkgqMLNvYGvepcpk3N8zcD1V84bW0qSxO7d tGn0cGXkskLBPYX/W9a0oIhCRXPcobfSQRnqd+Sqk/ksI8D4BfhpRvwUtZO6/4Mk4tQD KZw39GDY8cs/poPxF11lPUCM+TLMouBbDkGGi4LJwaPTVg//KOaXBefhEjjEwMxerKeH W2jA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=B9YMLBYcPz/4Hlblq1d7hFRHt4s8YknOklPReJ3GiyY=; b=hz18BNHtAc2CZ4PSQPa8AYdqMS7/qWBIrKsuVHa9WGWnfEhkP/0aIy+e0OffArgNh6 xDX8tb9j+e5AgnTu0uaGI4iU/b8SVBrnBw+PrUX8s18Y/EbCPcEmJ8LtksoAOVgf4N3K BVzerWPcvNB/c7BfhlG8tdFdU2SgxUxHmOksj7/LO4Vw93K5EHh+uoo5NxnZwLBegj3R JM0AuSvN0CR2mV1PG4Hk6ydOrMymH3CIUJOWjK5h9TGnkmvfNED6cun4bbZvT5LnfzTi zhVLqN0DzZ01DH+3qLBD9cI2qCW9xbCm4UfF2gq3gzMMHEHZrTQd9xhvUga+C01yJloE xliQ== X-Gm-Message-State: AO0yUKUtnWtcak3qrOD9RyOdfZvIy2oX4v3IKvEezh3vwQzbrTU4IGyK ynN1R1zqzv0SxUsxP4I+1LU= X-Google-Smtp-Source: AK7set/VuHemzuZuwIMi2PoQLRysxBPaa9lJAZXaeL3TFucxHl9nNqqCwNbM+Ex4gdZbddqGtL3HDA== X-Received: by 2002:ac2:5613:0:b0:4d6:ef7f:fc53 with SMTP id v19-20020ac25613000000b004d6ef7ffc53mr3252689lfd.34.1677082583575; Wed, 22 Feb 2023 08:16:23 -0800 (PST) Received: from dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi (dc75zzyyyyyyyyyyyyyyt-3.rev.dnainternet.fi. [2001:14ba:16f3:4a00::1]) by smtp.gmail.com with ESMTPSA id p1-20020a19f001000000b004d57ca1c96csm359052lfc.172.2023.02.22.08.16.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 22 Feb 2023 08:16:22 -0800 (PST) Date: Wed, 22 Feb 2023 18:16:18 +0200 From: Matti Vaittinen To: Matti Vaittinen , Matti Vaittinen Cc: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski , Matti Vaittinen , Shreeya Patel , Zhigang Shi , Paul Gazzillo , Dmitry Osipenko , Liam Beguin , Peter Rosin , Randy Dunlap , Masahiro Yamada , linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 6/6] MAINTAINERS: Add ROHM BU27034 Message-ID: <8dbdf68749389605cc4dff396ddd66d27fdb2a0d.1677080089.git.mazziesaccount@gmail.com> References: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: Precedence: bulk List-ID: X-Mailing-List: linux-iio@vger.kernel.org Add myself as a maintainer for ROHM BU27034 ALS driver. Signed-off-by: Matti Vaittinen --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 43f5a024daa2..8d31ef852372 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18090,6 +18090,11 @@ S: Maintained F: Documentation/devicetree/bindings/iio/light/bh1750.yaml F: drivers/iio/light/bh1750.c +ROHM BU27034 AMBIENT LIGHT SENSOR DRIVER +M: Matti Vaittinen +S: Supported +F: drivers/iio/light/rohm-bu27034.c + ROHM MULTIFUNCTION BD9571MWV-M PMIC DEVICE DRIVERS M: Marek Vasut L: linux-kernel@vger.kernel.org