Received: by 10.223.185.116 with SMTP id b49csp972417wrg; Wed, 21 Feb 2018 09:54:28 -0800 (PST) X-Google-Smtp-Source: AH8x225J+dclkKZ2ahcvFYQ2cIQlGH0XXjQic49jrEOvWRH636894mX5uki9INx1gZZa44Cv0al3 X-Received: by 10.98.150.82 with SMTP id c79mr4081056pfe.88.1519235668548; Wed, 21 Feb 2018 09:54:28 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1519235668; cv=none; d=google.com; s=arc-20160816; b=Ee5MbgzIfuEeXPGXX3nt37S7MX5m4Ih7xwDvkaQTGVr5xZnPKKVZ8JT3keeOVNpGnT KkRjyBg8N9wWkC61qQ2HatvdOMdA1EoulK60jFTyUlRMnGmM4g7yJnA2fn9yURyIbv+B RtUAkkgo8zGMmD4dhcfA4jhQNfSkcCe4sHNNZWhSrRUgkHPKFpiIa+GAq0lGzrIb9xv8 8UqZjKEpXnw8BMWuMIw/etd+v3wZtte/Ekvkchz+PHTNOEeqO8aSJ/5rzox2F/gObpme +8vjvF6M2kR+iIE7wCjCq+onlSn6vdrbPRMKMw3cj36p9TeQfXTn8kS9/peaR8KbcUWk QBFA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:to:cc:from:subject:message-id:date :mime-version:dkim-signature:arc-authentication-results; bh=Xw4aRTUk4icM6CvV1Vk+W+rAVgbR3T21blKmEhtX+fI=; b=f/ROzF+GrLs9CsMtCe8Uli8bvWbRKI0EkeEMWu4nbQdy/UxLjzsuqMV+gBLWfv1cQR 7c6xDYeNmFKjDSYPbSB4xZA/lrwaPe8oq7/5TvivdMNKoraFHFPbsd+XoGJDImXtVCen gOgNmPGg86dGSpmVd7zdQb+v7mA1biMZrLAkkrZ0ZW8INZISHgC1K0FC919cfmF3JTxu mldi+fE9Cdto1L/a39HF8u8sw5kHZCvE0l1wfFIuXG/oINdRMOeBt/LYlYg/KaSqHDDb u07oHDcmOfiYLbtlz374lyywKQGn/R+saAR0CB/+axndZbXriGNmTzwujh79As4mB4vf HmMQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=Cq0cOFCu; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id b9si1599705pff.42.2018.02.21.09.54.14; Wed, 21 Feb 2018 09:54:28 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@google.com header.s=20161025 header.b=Cq0cOFCu; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=REJECT sp=REJECT dis=NONE) header.from=google.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S934494AbeBUM4l (ORCPT + 99 others); Wed, 21 Feb 2018 07:56:41 -0500 Received: from mail-yw0-f202.google.com ([209.85.161.202]:34376 "EHLO mail-yw0-f202.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932772AbeBUM4h (ORCPT ); Wed, 21 Feb 2018 07:56:37 -0500 Received: by mail-yw0-f202.google.com with SMTP id z129so142821ywd.1 for ; Wed, 21 Feb 2018 04:56:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20161025; h=mime-version:date:message-id:subject:from:cc; bh=Xw4aRTUk4icM6CvV1Vk+W+rAVgbR3T21blKmEhtX+fI=; b=Cq0cOFCuC0X5hUhFNlj4M7woo6ei6CBViAyiqKa9c6Ly1jNE60C4v4eYFqLrqAVi0P cyHdOeT437yJdErqAi6pHHk+C0To/Tk9a2yiWCi6tBb/xh/dK6HgAv/6qImowZMmyRtv VAYL5bTML0NzwS7UJdB2yVIrGidzYa83nijr0XrOCU+syJQQHftEdRg55yB4vJpCQUN4 6VDg5xbT9jDGMurdVbglsig+g2xTadxdyLwVmyeVypm13RSndVG8yLHivZzssGAlaZyt /Df9HXXXhO6fi2MEbEykBo2v1ppuorDmWeXuhG4uC6wcnOrZpeYCtZZULFPelJl1LRXw MBaQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:date:message-id:subject:from:cc; bh=Xw4aRTUk4icM6CvV1Vk+W+rAVgbR3T21blKmEhtX+fI=; b=dpUgR2IMgbBYh1JNvKsunPB1Bz9P2vulvS2QWcMtYzL11JHwXaDBMxVxjcqzpHmioX lWJPlUfbj/H+jQGrpLzhrwV+kdGPs2Mu4HHoE60GyTdEwoQXQe9foDomMFwO5Wqrlph6 zU+If/cYfJMgsRgk7RfmwvOS8/LmslFGacAj7fdjgFbJ5xO0/zpBNSOHG5wNmokPwu4i +XwXk0jRKTYBEa2qKaG7pRN/JrYj4xwFDtls68d7cLOjvDzKgRycQlvyRlvrJ5hzevTS 6rrS0GKypA7XB3eblsap4piyVyhCgZ0ajm8vspTeLiAWNYHbLeozMfhWHmVTMTjDfd+y yo6A== X-Gm-Message-State: APf1xPBqSCwj7/y78RhgA1kw8i9YupVXE+uxh0A8Lsj8iulyKYd69jZY 0IN1EwzP8uRkAjEGNzneJvZ0x63YYzx2 MIME-Version: 1.0 X-Received: by 2002:a25:ab23:: with SMTP id u32-v6mr1661365ybi.83.1519217796604; Wed, 21 Feb 2018 04:56:36 -0800 (PST) Date: Wed, 21 Feb 2018 13:55:12 +0100 Message-Id: <20180221125512.8265-1-delroth@google.com> X-Mailer: git-send-email 2.16.1.291.g4437f3f132-goog Subject: [PATCH] iio: light: add driver for bh1730fvc chips From: Pierre Bourdon Cc: Jonathan Cameron , Hartmut Knaack , Lars-Peter Clausen , Peter Meerwald-Stadler , Pierre Bourdon , linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org Content-Type: text/plain; charset="UTF-8" To: unlisted-recipients:; (no To-header on input) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Ambient light sensor that supports visible light and IR measurements and configurable gain/integration time. Signed-off-by: Pierre Bourdon --- .../devicetree/bindings/iio/light/bh1730.txt | 15 + drivers/iio/light/Kconfig | 10 + drivers/iio/light/Makefile | 1 + drivers/iio/light/bh1730.c | 429 ++++++++++++++++++ 4 files changed, 455 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/light/bh1730.txt create mode 100644 drivers/iio/light/bh1730.c diff --git a/Documentation/devicetree/bindings/iio/light/bh1730.txt b/Documentation/devicetree/bindings/iio/light/bh1730.txt new file mode 100644 index 000000000000..6b38c4010647 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/bh1730.txt @@ -0,0 +1,15 @@ +* ROHM BH1730FVC ambient light sensor + +http://www.rohm.com/web/global/products/-/product/BH1730FVC + +Required properties: + + - compatible : should be "rohm,bh1730fvc" + - reg : the I2C address of the sensor + +Example: + +bh1730fvc@29 { + compatible = "rohm,bh1730fvc"; + reg = <0x29>; +}; diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 93fd421b10d7..286a7f78762b 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -63,6 +63,16 @@ config APDS9960 To compile this driver as a module, choose M here: the module will be called apds9960 +config BH1730 + tristate "ROHM BH1730 ambient light sensor" + depends on I2C + help + Say Y here to build support for the ROHM BH1730FVC ambient + light sensor. + + To compile this driver as a module, choose M here: the module will + be called bh1730. + config BH1750 tristate "ROHM BH1750 ambient light sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index f714067a7816..eb9010a10536 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_ADJD_S311) += adjd_s311.o obj-$(CONFIG_AL3320A) += al3320a.o obj-$(CONFIG_APDS9300) += apds9300.o obj-$(CONFIG_APDS9960) += apds9960.o +obj-$(CONFIG_BH1730) += bh1730.o obj-$(CONFIG_BH1750) += bh1750.o obj-$(CONFIG_BH1780) += bh1780.o obj-$(CONFIG_CM32181) += cm32181.o diff --git a/drivers/iio/light/bh1730.c b/drivers/iio/light/bh1730.c new file mode 100644 index 000000000000..6912aaa3295b --- /dev/null +++ b/drivers/iio/light/bh1730.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ROHM BH1730 ambient light sensor driver + * + * Copyright (c) 2018 Google, Inc. + * Author: Pierre Bourdon + * + * Based on a previous non-iio BH1730FVC driver: + * Copyright (C) 2012 Samsung Electronics. All rights reserved. + * Author: Won Huh + * + * Data sheets: + * http://www.rohm.com/web/global/datasheet/BH1730FVC/bh1730fvc-e + */ + +#include +#include +#include +#include +#include +#include + +#define BH1730_CMD_BIT BIT(7) + +#define BH1730_REG_CONTROL 0x00 +#define BH1730_REG_TIMING 0x01 +#define BH1730_REG_INTERRUPT 0x02 +#define BH1730_REG_THLLOW 0x03 +#define BH1730_REG_THLHIGH 0x04 +#define BH1730_REG_THHLOW 0x05 +#define BH1730_REG_THHHIGH 0x06 +#define BH1730_REG_GAIN 0x07 +#define BH1730_REG_ID 0x12 +#define BH1730_REG_DATA0LOW 0x14 +#define BH1730_REG_DATA0HIGH 0x15 +#define BH1730_REG_DATA1LOW 0x16 +#define BH1730_REG_DATA1HIGH 0x17 + +#define BH1730_CONTROL_POWER_ON BIT(0) +#define BH1730_CONTROL_MEASURE BIT(1) + +#define BH1730_INTERNAL_CLOCK_NS 2800ULL + +#define BH1730_DEFAULT_INTEG_MS 150 + +enum bh1730_gain { + BH1730_GAIN_1X = 0, + BH1730_GAIN_2X, + BH1730_GAIN_64X, + BH1730_GAIN_128X, +}; + +struct bh1730_data { + struct i2c_client *client; + enum bh1730_gain gain; + int itime; +}; + +static int bh1730_read_word(struct bh1730_data *bh1730, u8 reg) +{ + int ret = i2c_smbus_read_word_data(bh1730->client, + BH1730_CMD_BIT | reg); + if (ret < 0) + dev_err(&bh1730->client->dev, + "i2c read failed error %d, register %01x\n", + ret, reg); + return ret; +} + +static int bh1730_write(struct bh1730_data *bh1730, u8 reg, u8 val) +{ + int ret = i2c_smbus_write_byte_data(bh1730->client, + BH1730_CMD_BIT | reg, + val); + if (ret < 0) + dev_err(&bh1730->client->dev, + "i2c write failed error %d, register %01x\n", + ret, reg); + return ret; +} + +static int gain_setting_to_multiplier(enum bh1730_gain gain) +{ + switch (gain) { + case BH1730_GAIN_1X: + return 1; + case BH1730_GAIN_2X: + return 2; + case BH1730_GAIN_64X: + return 64; + case BH1730_GAIN_128X: + return 128; + default: + return -1; + } +} + +static int bh1730_gain_multiplier(struct bh1730_data *bh1730) +{ + int multiplier = gain_setting_to_multiplier(bh1730->gain); + + if (multiplier < 0) { + dev_warn(&bh1730->client->dev, + "invalid gain multiplier settings: %d\n", + bh1730->gain); + bh1730->gain = BH1730_GAIN_1X; + multiplier = 1; + } + + return multiplier; +} + +static u64 bh1730_itime_ns(struct bh1730_data *bh1730) +{ + return BH1730_INTERNAL_CLOCK_NS * 964 * (256 - bh1730->itime); +} + +static int bh1730_set_gain(struct bh1730_data *bh1730, enum bh1730_gain gain) +{ + int ret = bh1730_write(bh1730, BH1730_REG_GAIN, gain); + + if (ret < 0) + return ret; + + bh1730->gain = gain; + return 0; +} + +static int bh1730_set_integration_time_ms(struct bh1730_data *bh1730, + int time_ms) +{ + int ret; + u64 time_ns = time_ms * (u64)NSEC_PER_MSEC; + u64 itime_step_ns = BH1730_INTERNAL_CLOCK_NS * 964; + int itime = 256 - (int)DIV_ROUND_CLOSEST_ULL(time_ns, itime_step_ns); + + /* ITIME == 0 is reserved for manual integration mode. */ + if (itime <= 0 || itime > 255) { + dev_warn(&bh1730->client->dev, + "integration time out of range: %dms\n", time_ms); + return -ERANGE; + } + + ret = bh1730_write(bh1730, BH1730_REG_TIMING, itime); + if (ret < 0) + return ret; + + bh1730->itime = itime; + return 0; +} + +static void bh1730_wait_for_next_measurement(struct bh1730_data *bh1730) +{ + ndelay(bh1730_itime_ns(bh1730) + BH1730_INTERNAL_CLOCK_NS * 714); +} + +static int bh1730_adjust_gain(struct bh1730_data *bh1730) +{ + int visible, ir, highest, ret, i; + + visible = bh1730_read_word(bh1730, BH1730_REG_DATA0LOW); + if (visible < 0) + return visible; + + ir = bh1730_read_word(bh1730, BH1730_REG_DATA1LOW); + if (ir < 0) + return ir; + + highest = max(visible, ir); + + /* + * If the read value is being clamped, assume the worst and go to the + * lowest possible gain. The alternative is doing multiple + * recalibrations, which would be slower and have the same effect. + */ + if (highest == USHRT_MAX) + highest *= 128; + else + highest = (highest * 128) / bh1730_gain_multiplier(bh1730); + + /* + * Find the lowest gain multiplier which puts the measured values + * above 1024. This threshold is chosen to match the gap between 2X + * multiplier and 64X (next available) while keeping some margin. + */ + for (i = BH1730_GAIN_1X; i < BH1730_GAIN_128X; ++i) { + int adj = highest * gain_setting_to_multiplier(i) / 128; + + if (adj >= 1024) + break; + } + + if (i != bh1730->gain) { + ret = bh1730_set_gain(bh1730, i); + if (ret < 0) + return ret; + + bh1730_wait_for_next_measurement(bh1730); + } + return 0; +} + +static s64 bh1730_get_millilux(struct bh1730_data *bh1730) +{ + int visible, ir, visible_coef, ir_coef; + u64 itime_us = bh1730_itime_ns(bh1730) / NSEC_PER_USEC; + u64 millilux; + + visible = bh1730_read_word(bh1730, BH1730_REG_DATA0LOW); + if (visible < 0) + return visible; + + ir = bh1730_read_word(bh1730, BH1730_REG_DATA1LOW); + if (ir < 0) + return ir; + + if (ir * 1000 / visible < 500) { + visible_coef = 5002; + ir_coef = 7502; + } else if (ir * 1000 / visible < 754) { + visible_coef = 2250; + ir_coef = 2000; + } else if (ir * 1000 / visible < 1029) { + visible_coef = 1999; + ir_coef = 1667; + } else if (ir * 1000 / visible < 1373) { + visible_coef = 885; + ir_coef = 583; + } else if (ir * 1000 / visible < 1879) { + visible_coef = 309; + ir_coef = 165; + } else { + return 0; + } + + millilux = (u64)USEC_PER_MSEC * (visible_coef * visible - ir_coef * ir); + millilux /= bh1730_gain_multiplier(bh1730); + millilux *= 103; + millilux /= itime_us; + return millilux; +} + +static int bh1730_power_on(struct bh1730_data *bh1730) +{ + int ret; + int control = + BH1730_CONTROL_POWER_ON | + BH1730_CONTROL_MEASURE; + + ret = bh1730_write(bh1730, BH1730_REG_CONTROL, control); + if (ret < 0) + return ret; + return 0; +} + +static int bh1730_set_defaults(struct bh1730_data *bh1730) +{ + int ret; + + ret = bh1730_set_gain(bh1730, BH1730_GAIN_1X); + if (ret < 0) + return ret; + + ret = bh1730_set_integration_time_ms(bh1730, BH1730_DEFAULT_INTEG_MS); + if (ret < 0) + return ret; + + bh1730_wait_for_next_measurement(bh1730); + return 0; +} + +static int bh1730_power_off(struct bh1730_data *bh1730) +{ + return bh1730_write(bh1730, BH1730_REG_CONTROL, 0); +} + +static int bh1730_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct bh1730_data *bh1730 = iio_priv(indio_dev); + int ret; + s64 millilux; + + ret = bh1730_adjust_gain(bh1730); + if (ret < 0) + return ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + millilux = bh1730_get_millilux(bh1730); + if (millilux < 0) + return millilux; + *val = millilux / 1000; + *val2 = (millilux % 1000) * 1000; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_RAW: + switch (chan->channel2) { + case IIO_MOD_LIGHT_CLEAR: + ret = bh1730_read_word(bh1730, BH1730_REG_DATA0LOW); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + case IIO_MOD_LIGHT_IR: + ret = bh1730_read_word(bh1730, BH1730_REG_DATA1LOW); + if (ret < 0) + return ret; + *val = ret; + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + *val = bh1730_gain_multiplier(bh1730); + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static const struct iio_info bh1730_info = { + .read_raw = bh1730_read_raw, +}; + +static const struct iio_chan_spec bh1730_channels[] = { + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_BOTH, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + }, + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_CLEAR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_LIGHT, + .modified = 1, + .channel2 = IIO_MOD_LIGHT_IR, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int bh1730_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct bh1730_data *bh1730; + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct iio_dev *indio_dev; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1730)); + if (!indio_dev) + return -ENOMEM; + + bh1730 = iio_priv(indio_dev); + bh1730->client = client; + i2c_set_clientdata(client, indio_dev); + + ret = bh1730_power_on(bh1730); + if (ret < 0) + return ret; + + ret = bh1730_set_defaults(bh1730); + if (ret < 0) + return ret; + + indio_dev->dev.parent = &client->dev; + indio_dev->info = &bh1730_info; + indio_dev->name = "bh1730"; + indio_dev->channels = bh1730_channels; + indio_dev->num_channels = ARRAY_SIZE(bh1730_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = iio_device_register(indio_dev); + if (ret) + goto out_power_off; + return 0; + +out_power_off: + bh1730_power_off(bh1730); + return ret; +} + +static int bh1730_remove(struct i2c_client *client) +{ + struct iio_dev *indio_dev = i2c_get_clientdata(client); + struct bh1730_data *bh1730 = iio_priv(indio_dev); + + iio_device_unregister(indio_dev); + return bh1730_power_off(bh1730); +} + +static const struct i2c_device_id bh1730_id[] = { + { "bh1730", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bh1730_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_bh1730_match[] = { + { .compatible = "rohm,bh1730fvc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_bh1730_match); +#endif + +static struct i2c_driver bh1730_driver = { + .probe = bh1730_probe, + .remove = bh1730_remove, + .id_table = bh1730_id, + .driver = { + .name = "bh1730", + .of_match_table = of_match_ptr(of_bh1730_match), + }, +}; +module_i2c_driver(bh1730_driver); + +MODULE_AUTHOR("Pierre Bourdon "); +MODULE_DESCRIPTION("ROHM BH1730FVC driver"); +MODULE_LICENSE("GPL v2"); -- 2.16.1.291.g4437f3f132-goog