From patchwork Tue Apr 18 10:36:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Windfeldt-Prytz X-Patchwork-Id: 674691 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 163F4C7EE20 for ; Tue, 18 Apr 2023 10:36:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230012AbjDRKgx (ORCPT ); Tue, 18 Apr 2023 06:36:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:32976 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230208AbjDRKgx (ORCPT ); Tue, 18 Apr 2023 06:36:53 -0400 Received: from smtp2.axis.com (smtp2.axis.com [195.60.68.18]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 05F3419B5; Tue, 18 Apr 2023 03:36:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axis.com; q=dns/txt; s=axis-central1; t=1681814211; x=1713350211; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=a2pl3b3ZSbPe57NFlqGvBCFa/lR+C4jfxwmRWlYT6JE=; b=awLJkYU7eITpa+QG1R8Rl7ajCoIcV0DPx3Hg0+pe1xPu/+1OKCC4LO7A ccM+S+42hs9RGcNehUvk5VNxaEDfp/qCtb/nz/ADGLXf3/7gAyJ2+cwzd nf7j9bNYc5ot35YbXrTo3TX9CGJx7KZYQ7TIgOnu2EIQ67sDwdxmbiHf7 5ybyJdXpqRXRYVPnjkZYRtWPkAXemt3hT9zs6/jFq5J3jV7LrTVolUWpO RPIvxE2I4SFZ6y5k2omtqTuxIgVcFtgAXJdaJhGhqIKsfEJplKz7nKRPd CDqism9WQvxTYn9khXZOZyMSVVqTpOGG9OkpPoNGlSSJCZhnxPOoNkDMO g==; From: Stefan Windfeldt-Prytz Date: Tue, 18 Apr 2023 12:36:26 +0200 Subject: [PATCH v2 1/2] dt-bindings: Document TI OPT4001 light sensor bindings MIME-Version: 1.0 Message-ID: <20230323-add-opt4001-driver-v2-1-0bae0398669d@axis.com> References: <20230323-add-opt4001-driver-v2-0-0bae0398669d@axis.com> In-Reply-To: <20230323-add-opt4001-driver-v2-0-0bae0398669d@axis.com> To: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski CC: , , , Stefan Windfeldt-Prytz , X-Mailer: b4 0.12.2 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org Add devicetree bindings for opt4001 ambient light sensor. Signed-off-by: Stefan Windfeldt-Prytz --- .../devicetree/bindings/iio/light/ti,opt4001.yaml | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/light/ti,opt4001.yaml b/Documentation/devicetree/bindings/iio/light/ti,opt4001.yaml new file mode 100644 index 000000000000..43fd1a992aea --- /dev/null +++ b/Documentation/devicetree/bindings/iio/light/ti,opt4001.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/light/ti,opt4001.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments OPT4001 Ambient Light Sensor + +maintainers: + - Stefan Windfeldt-Prytz + +description: | + Ambient light sensor with an i2c interface. + Last part of compatible is for the packaging used. + Picostar is a 4 pinned SMT and sot-5x3 is a 8 pinned SOT. + Only sot-5x3 has an interrupt pin. + https://www.ti.com/lit/gpn/opt4001 + +properties: + compatible: + enum: + - ti,opt4001-picostar + - ti,opt4001-sot-5x3 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + vdd-supply: + description: Regulator that provides power to the sensor + +required: + - compatible + - reg + +allOf: + - if: + properties: + compatible: + contains: + const: ti,opt4001-sot-5x3 + then: + properties: + interrupts: + maxItems: 1 + else: + properties: + interrupts: false + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + light-sensor@44 { + compatible = "ti,opt4001-sot-5x3"; + reg = <0x44>; + vdd-supply = <&vdd_reg>; + interrupt-parent = <&gpio1>; + interrupts = <28 IRQ_TYPE_EDGE_FALLING>; + }; + }; +... From patchwork Tue Apr 18 10:36:27 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Windfeldt-Prytz X-Patchwork-Id: 675352 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 A739AC77B7F for ; Tue, 18 Apr 2023 10:36:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229544AbjDRKgv (ORCPT ); Tue, 18 Apr 2023 06:36:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:32944 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230012AbjDRKgt (ORCPT ); Tue, 18 Apr 2023 06:36:49 -0400 Received: from smtp2.axis.com (smtp2.axis.com [195.60.68.18]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 01DFB188; Tue, 18 Apr 2023 03:36:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=axis.com; q=dns/txt; s=axis-central1; t=1681814205; x=1713350205; h=from:date:subject:mime-version:content-transfer-encoding: message-id:references:in-reply-to:to:cc; bh=R7k23fUEAv9j9z7WgCJA8BSlDVYBNaSko201VlVXfRc=; b=SnqppqPJPlcCuBvVykJyWFFy5jBowiQO2bQdOYq9yMNxfTGoYClfoG9c E44mD1UHZkJmKvs5BeIZm5LYJ8NTKoxoelN3X3Ugm/ZhQKxc4CK1kvJX1 dkpZfiPxB4Vv3aAuo7bAA42eZv7OIhxwxq7pzQTfUd5b8XclHOCYr6cBG FXqyEYlP9obvjPDsaj1GP45yXY0s5Pk4qLBgonBC+i+6VswdSuhdZwy30 jJTX6ANpka/QD69+GmKgiSL5NYmysDS47BdjMQ65GeHvDkIs2B3FFsfZJ KLSbIVZ+ZbSBApmkRlZx/og2I7asHgTsakJ7zlAKnUgczr72RBFsx9lQO g==; From: Stefan Windfeldt-Prytz Date: Tue, 18 Apr 2023 12:36:27 +0200 Subject: [PATCH v2 2/2] iio: light: Add support for TI OPT4001 light sensor MIME-Version: 1.0 Message-ID: <20230323-add-opt4001-driver-v2-2-0bae0398669d@axis.com> References: <20230323-add-opt4001-driver-v2-0-0bae0398669d@axis.com> In-Reply-To: <20230323-add-opt4001-driver-v2-0-0bae0398669d@axis.com> To: Jonathan Cameron , Lars-Peter Clausen , Rob Herring , Krzysztof Kozlowski CC: , , , Stefan Windfeldt-Prytz , X-Mailer: b4 0.12.2 Precedence: bulk List-ID: X-Mailing-List: devicetree@vger.kernel.org This driver uses the continuous mode of the chip and integration time can be configured through sysfs. The constants for calculating lux value differs between packaging so it uses different compatible string for the two versions "ti,opt4001-picostar" and "ti,opt4001-sot-5x3" since the device id is the same. Datasheet: https://www.ti.com/lit/gpn/opt4001 Signed-off-by: Stefan Windfeldt-Prytz --- drivers/iio/light/Kconfig | 11 + drivers/iio/light/Makefile | 1 + drivers/iio/light/opt4001.c | 490 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 502 insertions(+) diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig index 0d4447df7200..c4064fb35f1f 100644 --- a/drivers/iio/light/Kconfig +++ b/drivers/iio/light/Kconfig @@ -399,6 +399,17 @@ config OPT3001 If built as a dynamically linked module, it will be called opt3001. +config OPT4001 + tristate "Texas Instruments OPT4001 Light Sensor" + depends on I2C + select REGMAP_I2C + help + If you say Y or M here, you get support for Texas Instruments + OPT4001 Ambient Light Sensor. + + If built as a dynamically linked module, it will be called + opt4001. + config PA12203001 tristate "TXC PA12203001 light and proximity sensor" depends on I2C diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile index 6f23817fae6f..4e0a908948db 100644 --- a/drivers/iio/light/Makefile +++ b/drivers/iio/light/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_MAX44000) += max44000.o obj-$(CONFIG_MAX44009) += max44009.o obj-$(CONFIG_NOA1305) += noa1305.o obj-$(CONFIG_OPT3001) += opt3001.o +obj-$(CONFIG_OPT4001) += opt4001.o obj-$(CONFIG_PA12203001) += pa12203001.o obj-$(CONFIG_RPR0521) += rpr0521.o obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o diff --git a/drivers/iio/light/opt4001.c b/drivers/iio/light/opt4001.c new file mode 100644 index 000000000000..e9f7b844b1f9 --- /dev/null +++ b/drivers/iio/light/opt4001.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Axis Communications AB + * + * Datasheet: https://www.ti.com/lit/gpn/opt4001 + * + * Device driver for the Texas Instruments OPT4001. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* OPT4001 register set */ +#define OPT4001_LIGHT1_MSB 0x00 +#define OPT4001_LIGHT1_LSB 0x01 +#define OPT4001_CTRL 0x0A +#define OPT4001_DEVICE_ID 0x11 + +/* OPT4001 register mask */ +#define OPT4001_EXPONENT_MASK GENMASK(15, 12) +#define OPT4001_MSB_MASK GENMASK(11, 0) +#define OPT4001_LSB_MASK GENMASK(15, 8) +#define OPT4001_COUNTER_MASK GENMASK(7, 4) +#define OPT4001_CRC_MASK GENMASK(3, 0) + +/* OPT4001 device id mask */ +#define OPT4001_DEVICE_ID_MASK GENMASK(11, 0) + +/* OPT4001 control registers mask */ +#define OPT4001_CTRL_QWAKE_MASK GENMASK(15, 15) +#define OPT4001_CTRL_RANGE_MASK GENMASK(13, 10) +#define OPT4001_CTRL_CONV_TIME_MASK GENMASK(9, 6) +#define OPT4001_CTRL_OPER_MODE_MASK GENMASK(5, 4) +#define OPT4001_CTRL_LATCH_MASK GENMASK(3, 3) +#define OPT4001_CTRL_INT_POL_MASK GENMASK(2, 2) +#define OPT4001_CTRL_FAULT_COUNT GENMASK(0, 1) + +/* OPT4001 constants */ +#define OPT4001_DEVICE_ID_VAL 0x121 + +/* OPT4001 operating modes */ +#define OPT4001_CTRL_OPER_MODE_OFF 0x0 +#define OPT4001_CTRL_OPER_MODE_FORCED 0x1 +#define OPT4001_CTRL_OPER_MODE_ONE_SHOT 0x2 +#define OPT4001_CTRL_OPER_MODE_CONTINUOUS 0x3 + +/* OPT4001 conversion control register definitions */ +#define OPT4001_CTRL_CONVERSION_0_6MS 0x0 +#define OPT4001_CTRL_CONVERSION_1MS 0x1 +#define OPT4001_CTRL_CONVERSION_1_8MS 0x2 +#define OPT4001_CTRL_CONVERSION_3_4MS 0x3 +#define OPT4001_CTRL_CONVERSION_6_5MS 0x4 +#define OPT4001_CTRL_CONVERSION_12_7MS 0x5 +#define OPT4001_CTRL_CONVERSION_25MS 0x6 +#define OPT4001_CTRL_CONVERSION_50MS 0x7 +#define OPT4001_CTRL_CONVERSION_100MS 0x8 +#define OPT4001_CTRL_CONVERSION_200MS 0x9 +#define OPT4001_CTRL_CONVERSION_400MS 0xa +#define OPT4001_CTRL_CONVERSION_800MS 0xb + +/* OPT4001 scale light level range definitions */ +#define OPT4001_CTRL_LIGHT_SCALE_AUTO 12 + +/* OPT4001 default values */ +#define OPT4001_DEFAULT_CONVERSION_TIME OPT4001_CTRL_CONVERSION_800MS + +/* The different packaging of OPT4001 has different constants used when calculating + * lux values. + */ +struct opt4001_chip_info { + int mul; + int div; + const char *name; +}; + +struct opt4001_settings { + /* Index of the chosen integration time */ + u8 int_time; + u8 chip_type; +}; + +struct opt4001_chip { + struct opt4001_settings light_settings; + struct regmap *regmap; + struct i2c_client *client; + const struct opt4001_chip_info *chip_info; +}; + +static const struct opt4001_chip_info opt4001_sot_5x3_info = { + .mul = 4375, + .div = 10000000, + .name = "opt4001-sot-5x3" +}; + +static const struct opt4001_chip_info opt4001_picostar_info = { + .mul = 3125, + .div = 10000000, + .name = "opt4001-picostar" +}; + +static const int opt4001_int_time_available[][2] = { + { 0, 600 }, + { 0, 1000 }, + { 0, 1800 }, + { 0, 3400 }, + { 0, 6500 }, + { 0, 12700 }, + { 0, 25000 }, + { 0, 50000 }, + { 0, 100000 }, + { 0, 200000 }, + { 0, 400000 }, + { 0, 800000 }, +}; + +/* + * Conversion time is integration time + time to set register + * this is used as integration time. + */ +static const int opt4001_int_time_reg[][2] = { + { 600, OPT4001_CTRL_CONVERSION_0_6MS }, + { 1000, OPT4001_CTRL_CONVERSION_1MS }, + { 1800, OPT4001_CTRL_CONVERSION_1_8MS }, + { 3400, OPT4001_CTRL_CONVERSION_3_4MS }, + { 6500, OPT4001_CTRL_CONVERSION_6_5MS }, + { 12700, OPT4001_CTRL_CONVERSION_12_7MS }, + { 25000, OPT4001_CTRL_CONVERSION_25MS }, + { 50000, OPT4001_CTRL_CONVERSION_50MS }, + { 100000, OPT4001_CTRL_CONVERSION_100MS }, + { 200000, OPT4001_CTRL_CONVERSION_200MS }, + { 400000, OPT4001_CTRL_CONVERSION_400MS }, + { 800000, OPT4001_CTRL_CONVERSION_800MS }, +}; + +static int opt4001_als_time_to_index(const u32 als_integration_time) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(opt4001_int_time_available); i++) { + if (als_integration_time == opt4001_int_time_available[i][1]) + return i; + } + + return -EINVAL; +} + +static u8 opt4001_calculate_crc(u8 exp, u32 mantissa, u8 count) +{ + u8 crc; + + crc = (hweight32(mantissa) + hweight32(exp) + hweight32(count)) % 2; + crc |= ((hweight32(mantissa & 0xAAAAA) + hweight32(exp & 0xA) + + hweight32(count & 0xA)) % 2) << 1; + crc |= ((hweight32(mantissa & 0x88888) + hweight32(exp & 0x8) + + hweight32(count & 0x8)) % 2) << 2; + crc |= (hweight32(mantissa & 0x80808) % 2) << 3; + + return crc; +} + +static int opt4001_read_lux_value(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + struct device *dev = &chip->client->dev; + unsigned int light1; + unsigned int light2; + u16 msb; + u16 lsb; + u8 exp; + u8 count; + u8 crc; + u8 calc_crc; + u64 lux_raw; + int ret; + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_MSB, &light1); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + ret = regmap_read(chip->regmap, OPT4001_LIGHT1_LSB, &light2); + if (ret < 0) { + dev_err(dev, "Failed to read data bytes"); + return ret; + } + + count = FIELD_GET(OPT4001_COUNTER_MASK, light2); + exp = FIELD_GET(OPT4001_EXPONENT_MASK, light1); + crc = FIELD_GET(OPT4001_CRC_MASK, light2); + msb = FIELD_GET(OPT4001_MSB_MASK, light1); + lsb = FIELD_GET(OPT4001_LSB_MASK, light2); + lux_raw = (msb << 8) + lsb; + calc_crc = opt4001_calculate_crc(exp, lux_raw, count); + if (calc_crc != crc) + return -EIO; + + lux_raw = lux_raw << exp; + lux_raw = lux_raw * chip->chip_info->mul; + *val = div_u64_rem(lux_raw, chip->chip_info->div, val2); + *val2 = *val2 * 100; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int opt4001_set_conf(struct opt4001_chip *chip) +{ + struct opt4001_settings light_settings = chip->light_settings; + struct device *dev = &chip->client->dev; + u16 reg; + int ret; + + reg = FIELD_PREP(OPT4001_CTRL_RANGE_MASK, OPT4001_CTRL_LIGHT_SCALE_AUTO); + reg |= FIELD_PREP(OPT4001_CTRL_CONV_TIME_MASK, light_settings.int_time); + reg |= FIELD_PREP(OPT4001_CTRL_OPER_MODE_MASK, OPT4001_CTRL_OPER_MODE_CONTINUOUS); + + ret = regmap_write(chip->regmap, OPT4001_CTRL, reg); + if (ret) + dev_err(dev, "Failed to set configuration\n"); + + return ret; +} + +static int opt4001_power_down(struct opt4001_chip *chip) +{ + int ret; + unsigned int reg; + struct device *dev = &chip->client->dev; + + ret = regmap_read(chip->regmap, OPT4001_DEVICE_ID, ®); + if (ret) { + dev_err(dev, "Failed to read configuration\n"); + return ret; + } + + /* MODE_OFF is 0x0 so just set bits to 0 */ + reg &= ~OPT4001_CTRL_OPER_MODE_MASK; + + ret = regmap_write(chip->regmap, OPT4001_CTRL, reg); + if (ret) + dev_err(dev, "Failed to set configuration to power down\n"); + + return ret; +} + +static void opt4001_chip_off_action(void *data) +{ + struct opt4001_chip *chip = data; + + opt4001_power_down(chip); +} + +static const struct iio_chan_spec opt4001_channels[] = { + { + .type = IIO_LIGHT, + .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME) + }, +}; + +static int opt4001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + ret = opt4001_read_lux_value(indio_dev, val, val2); + break; + case IIO_CHAN_INFO_INT_TIME: + *val = 0; + *val2 = opt4001_int_time_reg[chip->light_settings.int_time][0]; + ret = IIO_VAL_INT_PLUS_MICRO; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int opt4001_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct opt4001_chip *chip = iio_priv(indio_dev); + int int_time; + int ret; + + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + int_time = opt4001_als_time_to_index(val2); + if (int_time < 0) { + ret = int_time; + } else { + chip->light_settings.int_time = int_time; + ret = opt4001_set_conf(chip); + } + + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int opt4001_read_available(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_INT_TIME: + *length = ARRAY_SIZE(opt4001_int_time_available) * 2; + *vals = (const int *)opt4001_int_time_available; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_LIST; + + default: + return -EINVAL; + } +} + +static const struct iio_info opt4001_info_no_irq = { + .read_raw = opt4001_read_raw, + .write_raw = opt4001_write_raw, + .read_avail = opt4001_read_available, +}; + +static int opt4001_load_defaults(struct opt4001_chip *chip) +{ + chip->light_settings.int_time = OPT4001_DEFAULT_CONVERSION_TIME; + + return opt4001_set_conf(chip); +} + +static bool opt4001_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + case OPT4001_CTRL: + case OPT4001_DEVICE_ID: + return true; + default: + return false; + } +} + +static bool opt4001_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_CTRL: + return true; + default: + return false; + } +} + +static bool opt4001_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case OPT4001_LIGHT1_MSB: + case OPT4001_LIGHT1_LSB: + return true; + default: + return false; + } +} + +static const struct regmap_config opt4001_regmap_config = { + .name = "opt4001", + .reg_bits = 8, + .val_bits = 16, + .cache_type = REGCACHE_RBTREE, + .max_register = OPT4001_DEVICE_ID, + .readable_reg = opt4001_readable_reg, + .writeable_reg = opt4001_writable_reg, + .volatile_reg = opt4001_volatile_reg, + .val_format_endian = REGMAP_ENDIAN_BIG, +}; + +static int opt4001_probe(struct i2c_client *client) +{ + struct opt4001_chip *chip; + struct iio_dev *indio_dev; + int ret; + uint dev_id; + + indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + + ret = devm_regulator_get_enable(&client->dev, "vdd"); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to enable vdd supply\n"); + + chip->regmap = devm_regmap_init_i2c(client, &opt4001_regmap_config); + if (IS_ERR(chip->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(chip->regmap), + "regmap initialization failed\n"); + chip->client = client; + + indio_dev->info = &opt4001_info_no_irq; + + ret = regmap_reinit_cache(chip->regmap, &opt4001_regmap_config); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to reinit regmap cache\n"); + + ret = regmap_read(chip->regmap, OPT4001_DEVICE_ID, &dev_id); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to read the device ID register\n"); + + dev_id = FIELD_GET(OPT4001_DEVICE_ID_MASK, dev_id); + if (dev_id != OPT4001_DEVICE_ID_VAL) { + dev_err(&client->dev, "Device ID: %#04x unknown\n", dev_id); + return -EINVAL; + } + + chip->chip_info = device_get_match_data(&client->dev); + + indio_dev->channels = opt4001_channels; + indio_dev->num_channels = ARRAY_SIZE(opt4001_channels); + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->name = chip->chip_info->name; + + ret = opt4001_load_defaults(chip); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to set sensor defaults\n"); + + ret = devm_add_action_or_reset(&client->dev, + opt4001_chip_off_action, + chip); + if (ret < 0) { + dev_err(&client->dev, "Failed to setup power off action %d\n", + ret); + return ret; + } + + return devm_iio_device_register(&client->dev, indio_dev); +} + +/* + * The compatible string determines which constants to use depending on + * opt4001 packaging + */ +static const struct i2c_device_id opt4001_id[] = { + { "opt4001-sot-5x3", (kernel_ulong_t)&opt4001_sot_5x3_info }, + { "opt4001-picostar", (kernel_ulong_t)&opt4001_picostar_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, opt4001_id); + +static const struct of_device_id opt4001_of_match[] = { + { .compatible = "ti,opt4001-sot-5x3", .data = &opt4001_sot_5x3_info}, + { .compatible = "ti,opt4001-picostar", .data = &opt4001_picostar_info}, + {} +}; +MODULE_DEVICE_TABLE(of, opt4001_of_match); + +static struct i2c_driver opt4001_driver = { + .driver = { + .name = "opt4001", + .of_match_table = opt4001_of_match, + }, + .probe_new = opt4001_probe, + .id_table = opt4001_id, +}; +module_i2c_driver(opt4001_driver); + +MODULE_AUTHOR("Stefan Windfeldt-Prytz "); +MODULE_DESCRIPTION("Texas Instruments opt4001 ambient light sensor driver"); +MODULE_LICENSE("GPL");