iio: sensors: Add BH1730FVC driver
David Yu [Fri, 19 Jun 2015 06:48:59 +0000 (15:48 +0900)]
Bug 200113585

Change-Id: I7379f1d2a94dd0c2e30698768edc85796bd8c27d
Signed-off-by: David Yu <davyu@nvidia.com>
Reviewed-on: http://git-master/r/760011
Reviewed-by: Automatic_Commit_Validation_User
Reviewed-by: Erik Lilliebjerg <elilliebjerg@nvidia.com>
Reviewed-by: Robert Collins <rcollins@nvidia.com>

Documentation/devicetree/bindings/iio/bh1730fvc-als.txt [new file with mode: 0644]
drivers/iio/light/Kconfig
drivers/iio/light/Makefile
drivers/iio/light/nvs_bh1730fvc.c [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/iio/bh1730fvc-als.txt b/Documentation/devicetree/bindings/iio/bh1730fvc-als.txt
new file mode 100644 (file)
index 0000000..c7fd6c5
--- /dev/null
@@ -0,0 +1,26 @@
+* ROHM BH1730FVC ambient light sensor
+
+nvs_ drivers use the NVidia Sensor (NVS) framework.
+See the nvs.txt documentation for NVS DT capabilities.
+
+Required properties:
+- compatible: must be "rohm,bh1730fvc"
+- reg: i2c address of the device. It should be 0x29.
+
+Optional properties:
+- status: set to "ok" or "okay" for normal operation.  Set to anything else
+         to unload the driver without ever communicating with the device.
+  Note: The "anything else" above is typically "disabled".
+       Since the driver will unload without communicating with the device, the
+       device will be left in its POR state.
+- vdd-supply: regulator supply for the chip vcc
+- vid-supply: regulator supply for the chip dvi
+  Note: This is required if the driver is to control the regulator.
+
+
+Example:
+                bh1730fvc@29 {
+                        compatible = "rohm,bh1730fvc";
+                        reg = <0x29>;
+                };
+
index 64c4f27..1cf4896 100644 (file)
@@ -63,6 +63,20 @@ config HID_SENSOR_ALS
          Say yes here to build support for the HID SENSOR
          Ambient light sensor.
 
+config NVS_BH1730FVC
+       tristate "ROHM BH1730FVC ALS"
+       select NVS_IIO
+       select NVS_LIGHT
+       select IIO_BUFFER
+       select IIO_TRIGGERED_BUFFER
+       depends on I2C
+       help
+         Say Y here for ROHM Co., Ltd. BH1730FVC
+         Digital 16bit Serial Output Type
+         Ambient Light Sensor (ALS) support.
+
+         The driver uses the NVS (NVidia Sensor) IIO, light framework.
+
 config NVS_CM3217
        tristate "Capella CM3217 ALS"
        select NVS_IIO
index 4aa1e43..f95e8f1 100644 (file)
@@ -10,6 +10,7 @@ obj-$(CONFIG_SENSORS_LM3533)  += lm3533-als.o
 obj-$(CONFIG_SENSORS_TSL2563)  += tsl2563.o
 obj-$(CONFIG_VCNL4000)         += vcnl4000.o
 obj-$(CONFIG_HID_SENSOR_ALS)   += hid-sensor-als.o
+obj-$(CONFIG_NVS_BH1730FVC)    += nvs_bh1730fvc.o
 obj-$(CONFIG_NVS_CM3217)       += nvs_cm3217.o
 obj-$(CONFIG_NVS_CM3218)       += nvs_cm3218.o
 obj-$(CONFIG_NVS_MAX4400X)     += nvs_max4400x.o
diff --git a/drivers/iio/light/nvs_bh1730fvc.c b/drivers/iio/light/nvs_bh1730fvc.c
new file mode 100644 (file)
index 0000000..cbd0a33
--- /dev/null
@@ -0,0 +1,762 @@
+/* Copyright (c) 2015, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/* The NVS = NVidia Sensor framework */
+/* See nvs_iio.c and nvs.h for documentation */
+/* See nvs_light.c and nvs_light.h for documentation */
+
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/debugfs.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <linux/delay.h>
+#include <linux/ktime.h>
+#include <linux/string.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/of.h>
+#include <linux/nvs.h>
+#include <linux/nvs_light.h>
+
+#define BH1730_VENDOR                  "ROHM"
+#define BH1730_NAME                    "bh1730fvc"
+#define BH1730_LIGHT_VERSION           (1)
+#define BH1730_LIGHT_MAX_RANGE_IVAL    (100000)
+#define BH1730_LIGHT_MAX_RANGE_MICRO   (0)
+#define BH1730_LIGHT_RESOLUTION_IVAL   (1)
+#define BH1730_LIGHT_RESOLUTION_MICRO  (0)
+#define BH1730_LIGHT_MILLIAMP_IVAL     (0)
+#define BH1730_LIGHT_MILLIAMP_MICRO    (90000)
+#define BH1730_LIGHT_SCALE_IVAL                (0)
+#define BH1730_LIGHT_SCALE_MICRO       (0)
+#define BH1730_LIGHT_OFFSET_IVAL       (0)
+#define BH1730_LIGHT_OFFSET_MICRO      (0)
+#define BH1730_LIGHT_THRESHOLD_LO      (1)
+#define BH1730_LIGHT_THRESHOLD_HI      (1)
+#define BH1730_POLL_DLY_US_MIN         (2700)
+#define BH1730_POLL_DLY_US_MAX         (688500)
+
+/* HW registers */
+#define BH1730_COMMAND                 (0x80)
+
+#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 BIT_ADC_INTR   0x20
+#define BIT_ADC_VALID  0x10
+#define BIT_ONE_TIME   0x08
+#define BIT_DATA_SEL   0x04
+#define BIT_ADC_EN     0x02
+#define BIT_POWER      0x01
+
+#define BH1730_HW_DELAY_MS             (10)
+
+enum BH1730_GAIN {
+       BH1730_GAIN_X1 = 0,
+       BH1730_GAIN_X2,
+       BH1730_GAIN_X64,
+       BH1730_GAIN_X128,
+};
+
+/* regulator names in order of powering on */
+static char *bh1730_vregs[] = {
+       "vdd",
+       "vid",
+};
+
+struct bh1730_state {
+       struct i2c_client *i2c;
+       struct nvs_fn_if *nvs;
+       void *nvs_st;
+       struct sensor_cfg cfg;
+       struct delayed_work dw;
+       struct regulator_bulk_data vreg[ARRAY_SIZE(bh1730_vregs)];
+       struct nvs_light light;
+       unsigned int sts;               /* debug flags */
+       unsigned int errs;              /* error count */
+       unsigned int enabled;           /* enable status */
+       bool hw_change;                 /* HW changed so drop first sample */
+       u32 itime_us;
+       u32 gain;
+       u16 data0;
+       u16 data1;
+       u8 cached_reg_addr;
+       int always_on;
+};
+
+static s64 bh1730_get_time_ns(void)
+{
+       struct timespec ts;
+
+       ktime_get_ts(&ts);
+       return timespec_to_ns(&ts);
+}
+
+static int bh1730_i2c_wr(struct bh1730_state *st, u8 reg, u8 val)
+{
+       int ret;
+       ret = i2c_smbus_write_byte_data(st->i2c, reg | BH1730_COMMAND, val);
+       if (ret)
+               return ret;
+
+       if (reg == BH1730_REG_GAIN) {
+               if (val == BH1730_GAIN_X1)
+                       st->gain = 1;
+               else if (val == BH1730_GAIN_X2)
+                       st->gain = 2;
+               else if (val == BH1730_GAIN_X64)
+                       st->gain = 64;
+               else if (val == BH1730_GAIN_X128)
+                       st->gain = 128;
+       } else if (reg == BH1730_REG_TIMING)
+               st->itime_us = (256 - val) * 2700;
+
+       return 0;
+}
+
+static int bh1730_i2c_rd(struct bh1730_state *st, u8 reg, u8 *val)
+{
+       int ret;
+       ret = i2c_smbus_read_byte_data(st->i2c, reg | BH1730_COMMAND);
+       if (ret < 0)
+               return -EIO;
+       *val = (u8)ret;
+       return 0;
+}
+
+static int bh1730_read_n(struct bh1730_state *st, u8 reg, u8 *val, int n)
+{
+       int ret;
+       ret = i2c_smbus_read_i2c_block_data(st->i2c, reg | BH1730_COMMAND,
+                                           n, val);
+       if (ret != n)
+               return -EIO;
+       return 0;
+}
+
+static int bh1730_rd_data(struct bh1730_state *st, u16 *val)
+{
+       u8 data[4];
+       if (bh1730_read_n(st, BH1730_REG_DATA0LOW, data, 4)) {
+               dev_err(&st->i2c->dev, "%s: bh1730_read_n failure\n", __func__);
+               return -EIO;
+       } else {
+               val[0] = ((data[1] << 8) | data[0]);
+               val[1] = ((data[3] << 8) | data[2]);
+               st->data0 = val[0];
+               st->data1 = val[1];
+       }
+
+       return 0;
+}
+
+static int bh1730_pm(struct bh1730_state *st, bool enable)
+{
+       int ret;
+
+       if (enable) {
+               ret = nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                      ARRAY_SIZE(bh1730_vregs));
+               if (ret)
+                       mdelay(BH1730_HW_DELAY_MS);
+               ret = bh1730_i2c_wr(st, BH1730_REG_CONTROL,
+                                   BIT_ADC_EN | BIT_POWER);
+       } else {
+               ret = nvs_vregs_sts(st->vreg, ARRAY_SIZE(bh1730_vregs));
+               if ((ret < 0) || (ret == ARRAY_SIZE(bh1730_vregs))) {
+                       ret = bh1730_i2c_wr(st, BH1730_REG_CONTROL, 0);
+               } else if (ret > 0) {
+                       nvs_vregs_enable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(bh1730_vregs));
+                       mdelay(BH1730_HW_DELAY_MS);
+                       ret = bh1730_i2c_wr(st, BH1730_REG_CONTROL, 0);
+               }
+               ret |= nvs_vregs_disable(&st->i2c->dev, st->vreg,
+                                        ARRAY_SIZE(bh1730_vregs));
+       }
+       if (ret > 0)
+               ret = 0;
+       if (ret) {
+               dev_err(&st->i2c->dev, "%s pwr=%x ERR=%d\n",
+                       __func__, enable, ret);
+       } else {
+               if (st->sts & NVS_STS_SPEW_MSG)
+                       dev_info(&st->i2c->dev, "%s pwr=%x\n",
+                                __func__, enable);
+       }
+       return ret;
+}
+
+static void bh1730_pm_exit(struct bh1730_state *st)
+{
+       bh1730_pm(st, false);
+       nvs_vregs_exit(&st->i2c->dev, st->vreg, ARRAY_SIZE(bh1730_vregs));
+}
+
+static int bh1730_pm_init(struct bh1730_state *st)
+{
+       int ret;
+
+       st->enabled = 0;
+       nvs_vregs_init(&st->i2c->dev,
+                      st->vreg, ARRAY_SIZE(bh1730_vregs), bh1730_vregs);
+       /* on off for low power mode if regulator still on */
+       ret = bh1730_pm(st, true);
+       ret |= bh1730_pm(st, false);
+       return ret;
+}
+
+static int bh1730_init(struct bh1730_state *st)
+{
+       int ret;
+
+       /* itime 100ms */
+       ret = bh1730_i2c_wr(st, BH1730_REG_TIMING, 38);
+       /* gain X1 */
+       ret |= bh1730_i2c_wr(st, BH1730_REG_GAIN, BH1730_GAIN_X1);
+       if (!ret)
+               st->hw_change = true;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&st->i2c->dev, "%s itime_us=%d gain=%d err=%d\n",
+                        __func__, st->itime_us, st->gain, ret);
+       return ret;
+}
+
+static u32 bh1730_get_lux(struct bh1730_state *st, u16 *data)
+{
+       u32 lux;
+       if (data[1] * 100 / data[0] < 26)
+               lux = 1290 * data[0] - 2733 * data[1];
+       if (data[1] * 100 / data[0] < 55)
+               lux = 795 * data[0] - 859 * data[1];
+       if (data[1] * 100 / data[0] < 109)
+               lux = 510 * data[0] - 345 * data[1];
+       if (data[1] * 100 / data[0] < 213)
+               lux = 276 * data[0] - 130 * data[1];
+       else
+               return 0;
+       lux = lux * 100000 / (st->gain * st->itime_us);
+
+       return lux;
+}
+
+static int bh1730_rd(struct bh1730_state *st)
+{
+       u16 hw[2];
+       s64 ts;
+       struct nvs_light *nl = &st->light;
+       int ret;
+
+       if (st->hw_change) {
+               /* drop first sample after HW change */
+               st->hw_change = false;
+               return 0;
+       }
+
+       ret = bh1730_rd_data(st, hw);
+       if (ret)
+               return ret;
+
+       ts = bh1730_get_time_ns();
+       nl->hw = bh1730_get_lux(st, hw);
+       nl->timestamp = ts;
+       nvs_light_read(nl);
+
+       if (nl->nld_i_change)
+               bh1730_init(st);
+       return 0;
+}
+
+static void bh1730_read(struct bh1730_state *st)
+{
+       st->nvs->nvs_mutex_lock(st->nvs_st);
+       if (st->enabled) {
+               bh1730_rd(st);
+               schedule_delayed_work(&st->dw,
+                                   msecs_to_jiffies(st->light.poll_delay_ms));
+       }
+       st->nvs->nvs_mutex_unlock(st->nvs_st);
+}
+
+static void bh1730_work(struct work_struct *ws)
+{
+       struct bh1730_state *st = container_of((struct delayed_work *)ws,
+                                          struct bh1730_state, dw);
+
+       bh1730_read(st);
+}
+
+static int bh1730_disable(struct bh1730_state *st)
+{
+       int ret;
+
+       if (st->always_on)
+               return 0;
+
+       cancel_delayed_work(&st->dw);
+       ret = bh1730_pm(st, false);
+       if (!ret)
+               st->enabled = 0;
+       return ret;
+}
+
+static int bh1730_enable(void *client, int snsr_id, int enable)
+{
+       struct bh1730_state *st = (struct bh1730_state *)client;
+       unsigned int ms;
+       int ret;
+
+       if (enable < 0)
+               return st->enabled;
+
+       if (enable) {
+               ret = bh1730_pm(st, true);
+               if (!ret) {
+                       ret = bh1730_init(st);
+                       st->light.cfg->delay_us_min = st->itime_us / 1000;
+                       st->light.cfg->delay_us_max = st->itime_us / 1000;
+                       nvs_light_enable(&st->light);
+                       if (ret) {
+                               bh1730_disable(st);
+                       } else {
+                               st->enabled = 1;
+                               ms = st->light.poll_delay_ms;
+                               schedule_delayed_work(&st->dw,
+                                                     msecs_to_jiffies(ms));
+                       }
+               }
+       } else {
+               ret = bh1730_disable(st);
+       }
+       return ret;
+}
+
+static int bh1730_batch(void *client, int snsr_id, int flags,
+                   unsigned int period, unsigned int timeout)
+{
+       struct bh1730_state *st = (struct bh1730_state *)client;
+
+       if (timeout)
+               /* timeout not supported (no HW FIFO) */
+               return -EINVAL;
+
+       st->light.delay_us = period;
+       return 0;
+}
+
+static int bh1730_regs(void *client, int snsr_id, char *buf)
+{
+       struct bh1730_state *st = (struct bh1730_state *)client;
+       ssize_t t;
+       int ret;
+       u8 readval;
+       u8 i;
+       u8 regs[] = {
+               BH1730_REG_CONTROL,
+               BH1730_REG_TIMING,
+               BH1730_REG_INTERRUPT,
+               BH1730_REG_THLLOW,
+               BH1730_REG_THLHIGH,
+               BH1730_REG_THHLOW,
+               BH1730_REG_THHHIGH,
+               BH1730_REG_GAIN,
+               BH1730_REG_ID,
+               BH1730_REG_DATA0LOW,
+               BH1730_REG_DATA0HIGH,
+               BH1730_REG_DATA1LOW,
+               BH1730_REG_DATA1HIGH
+       };
+
+       t = sprintf(buf, "lux: %u\n", st->light.lux);
+       t += sprintf(buf, "registers:\n");
+       for (i = 0; i < ARRAY_SIZE(regs); i++) {
+               ret = bh1730_i2c_rd(st, regs[i], &readval);
+               if (ret)
+                       t += sprintf(buf + t, "0x%hhx=ERR\n", i);
+               else
+                       t += sprintf(buf + t, "0x%hhx=0x%hhx\n", i, readval);
+       }
+       return t;
+}
+
+static int bh1730_nvs_write(void *client, int snsr_id, unsigned int nvs)
+{
+       struct bh1730_state *st = (struct bh1730_state *)client;
+       u8 reg;
+       u8 val;
+
+       reg = (nvs >> 16) && 0xff;
+       val = (nvs >> 8) && 0xff;
+       return bh1730_i2c_wr(st, reg, val);
+}
+
+static int bh1730_nvs_read(void *client, int snsr_id, char *buf)
+{
+       struct bh1730_state *st = (struct bh1730_state *)client;
+
+       return nvs_light_dbg(&st->light, buf);
+}
+
+static struct nvs_fn_dev bh1730_fn_dev = {
+       .enable                         = bh1730_enable,
+       .batch                          = bh1730_batch,
+       .regs                           = bh1730_regs,
+       .nvs_write                      = bh1730_nvs_write,
+       .nvs_read                       = bh1730_nvs_read,
+};
+
+static int bh1730_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct bh1730_state *st = i2c_get_clientdata(client);
+       int ret = 0;
+
+       if (st->always_on)
+               return 0;
+
+       st->sts |= NVS_STS_SUSPEND;
+       if (st->nvs && st->nvs_st)
+               ret = st->nvs->suspend(st->nvs_st);
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+       return ret;
+}
+
+static int bh1730_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct bh1730_state *st = i2c_get_clientdata(client);
+       int ret = 0;
+
+       if (st->always_on)
+               return 0;
+
+       if (st->nvs && st->nvs_st)
+               ret = st->nvs->resume(st->nvs_st);
+       st->sts &= ~NVS_STS_SUSPEND;
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+       return ret;
+}
+
+static SIMPLE_DEV_PM_OPS(bh1730_pm_ops, bh1730_suspend, bh1730_resume);
+
+static void bh1730_shutdown(struct i2c_client *client)
+{
+       struct bh1730_state *st = i2c_get_clientdata(client);
+
+       st->sts |= NVS_STS_SHUTDOWN;
+       if (st->nvs && st->nvs_st)
+               st->nvs->shutdown(st->nvs_st);
+       if (st->sts & NVS_STS_SPEW_MSG)
+               dev_info(&client->dev, "%s\n", __func__);
+}
+
+static int bh1730_remove(struct i2c_client *client)
+{
+       struct bh1730_state *st = i2c_get_clientdata(client);
+
+       if (st != NULL) {
+               bh1730_shutdown(client);
+               if (st->nvs && st->nvs_st)
+                       st->nvs->remove(st->nvs_st);
+               if (st->dw.wq)
+                       destroy_workqueue(st->dw.wq);
+               bh1730_pm_exit(st);
+       }
+       dev_info(&client->dev, "%s\n", __func__);
+       return 0;
+}
+
+static struct sensor_cfg bh1730_cfg_dflt = {
+       .name                   = NVS_LIGHT_STRING,
+       .ch_n                   = 1,
+       .ch_sz                  = 4,
+       .part                   = BH1730_NAME,
+       .vendor                 = BH1730_VENDOR,
+       .version                = BH1730_LIGHT_VERSION,
+       .max_range              = {
+               .ival           = BH1730_LIGHT_MAX_RANGE_IVAL,
+               .fval           = BH1730_LIGHT_MAX_RANGE_MICRO,
+       },
+       .resolution             = {
+               .ival           = BH1730_LIGHT_RESOLUTION_IVAL,
+               .fval           = BH1730_LIGHT_RESOLUTION_MICRO,
+       },
+       .milliamp               = {
+               .ival           = BH1730_LIGHT_MILLIAMP_IVAL,
+               .fval           = BH1730_LIGHT_MILLIAMP_MICRO,
+       },
+       .delay_us_min           = BH1730_POLL_DLY_US_MIN,
+       .delay_us_max           = BH1730_POLL_DLY_US_MAX,
+       .flags                  = SENSOR_FLAG_ON_CHANGE_MODE,
+       .scale                  = {
+               .ival           = BH1730_LIGHT_SCALE_IVAL,
+               .fval           = BH1730_LIGHT_SCALE_MICRO,
+       },
+       .thresh_lo              = BH1730_LIGHT_THRESHOLD_LO,
+       .thresh_hi              = BH1730_LIGHT_THRESHOLD_HI,
+};
+
+static int bh1730_of_dt(struct bh1730_state *st, struct device_node *dn)
+{
+       int ret;
+
+       /* default NVS programmable parameters */
+       memcpy(&st->cfg, &bh1730_cfg_dflt, sizeof(st->cfg));
+       st->light.cfg = &st->cfg;
+       st->light.hw_mask = 0xFFFF;
+       /* device tree parameters */
+
+       if (dn) {
+               ret = nvs_of_dt(dn, &st->cfg, NULL);
+               if (ret == -ENODEV)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static ssize_t bh1730_debugfs_read_lux(struct file *file, char __user *userbuf,
+                             size_t count, loff_t *ppos)
+{
+       struct bh1730_state *st = file->private_data;
+       char buf[20];
+       ssize_t len;
+
+       len = snprintf(buf, sizeof(buf), "%u\n", st->light.lux);
+
+       return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static const struct file_operations bh1730_debugfs_lux_fops = {
+       .open = simple_open,
+       .read = bh1730_debugfs_read_lux,
+};
+
+static ssize_t bh1730_debugfs_read_reg(struct file *file, char __user *userbuf,
+                             size_t count, loff_t *ppos)
+{
+       struct bh1730_state *st = file->private_data;
+       char buf[20];
+       u8 val = 0;
+       ssize_t len;
+       int ret;
+
+       ret = bh1730_i2c_rd(st, st->cached_reg_addr, &val);
+       if (ret)
+               dev_err(&st->i2c->dev, "%s: read failed\n", __func__);
+
+       len = snprintf(buf, sizeof(buf), "0x%X\n", val);
+
+       return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static ssize_t bh1730_debugfs_write_reg(struct file *file,
+                    const char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct bh1730_state *st = file->private_data;
+       unsigned reg, val;
+       char buf[80];
+       int ret;
+
+       count = min_t(size_t, count, (sizeof(buf)-1));
+       if (copy_from_user(buf, userbuf, count))
+               return -EFAULT;
+
+       buf[count] = 0;
+
+       ret = sscanf(buf, "%i %i", &reg, &val);
+
+       switch (ret) {
+       case 1:
+               st->cached_reg_addr = reg;
+               break;
+       case 2:
+               st->cached_reg_addr = reg;
+               return bh1730_i2c_wr(st, (u8)reg, (u8)val);
+               if (ret) {
+                       dev_err(&st->i2c->dev, "%s: write failed\n", __func__);
+                       return ret;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return count;
+}
+
+static const struct file_operations bh1730_debugfs_reg_fops = {
+       .open = simple_open,
+       .read = bh1730_debugfs_read_reg,
+       .write = bh1730_debugfs_write_reg,
+};
+
+static ssize_t bh1730_debugfs_read_alwayson(struct file *file,
+                       char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct bh1730_state *st = file->private_data;
+       char buf[16];
+       ssize_t len;
+
+       len = snprintf(buf, sizeof(buf), "%d\n", st->always_on);
+
+       return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static ssize_t bh1730_debugfs_write_alwayson(struct file *file,
+                    const char __user *userbuf, size_t count, loff_t *ppos)
+{
+       struct bh1730_state *st = file->private_data;
+       int val;
+       char buf[16];
+
+       count = min_t(size_t, count, (sizeof(buf)-1));
+       if (copy_from_user(buf, userbuf, count))
+               return -EFAULT;
+
+       buf[count] = 0;
+
+       if (!kstrtoint(buf, 0, &val))
+               st->always_on = val;
+
+       if (st->always_on && !st->enabled)
+               bh1730_enable(st, 0, 1);
+
+       return count;
+}
+
+static const struct file_operations bh1730_debugfs_alwayson_fops = {
+       .open = simple_open,
+       .read = bh1730_debugfs_read_alwayson,
+       .write = bh1730_debugfs_write_alwayson,
+};
+
+static int bh1730_probe(struct i2c_client *client,
+                   const struct i2c_device_id *id)
+{
+       struct bh1730_state *st;
+       struct dentry *debugfs_root;
+       int ret;
+
+       dev_info(&client->dev, "%s\n", __func__);
+       st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+       if (st == NULL) {
+               dev_err(&client->dev, "%s devm_kzalloc ERR\n", __func__);
+               return -ENOMEM;
+       }
+
+       i2c_set_clientdata(client, st);
+       st->i2c = client;
+       ret = bh1730_of_dt(st, client->dev.of_node);
+       if (ret) {
+               if (ret == -ENODEV) {
+                       dev_info(&client->dev, "%s DT disabled\n", __func__);
+               } else {
+                       dev_err(&client->dev, "%s _of_dt ERR\n", __func__);
+                       ret = -ENODEV;
+               }
+               goto bh1730_probe_exit;
+       }
+
+       ret = bh1730_pm_init(st);
+       if (ret) {
+               dev_err(&client->dev, "%s _pm_init ERR\n", __func__);
+               ret = -ENODEV;
+               goto bh1730_probe_exit;
+       }
+
+       bh1730_fn_dev.errs = &st->errs;
+       bh1730_fn_dev.sts = &st->sts;
+       st->nvs = nvs_iio();
+       if (st->nvs == NULL) {
+               dev_err(&client->dev, "%s nvs_iio ERR\n", __func__);
+               ret = -ENODEV;
+               goto bh1730_probe_exit;
+       }
+
+       ret = st->nvs->probe(&st->nvs_st, st, &client->dev,
+                            &bh1730_fn_dev, &st->cfg);
+       if (ret) {
+               dev_err(&client->dev, "%s nvs_probe ERR\n", __func__);
+               ret = -ENODEV;
+               goto bh1730_probe_exit;
+       }
+
+       debugfs_root = debugfs_create_dir(BH1730_NAME, NULL);
+       if (debugfs_root) {
+               debugfs_create_file("lux", 0444, debugfs_root, st,
+                                   &bh1730_debugfs_lux_fops);
+               debugfs_create_file("reg", 0644, debugfs_root, st,
+                                   &bh1730_debugfs_reg_fops);
+               debugfs_create_file("alwayson", 0644, debugfs_root, st,
+                                   &bh1730_debugfs_alwayson_fops);
+       }
+
+       st->light.nvs_st = st->nvs_st;
+       INIT_DELAYED_WORK(&st->dw, bh1730_work);
+       dev_info(&client->dev, "%s done\n", __func__);
+       return 0;
+
+bh1730_probe_exit:
+       bh1730_remove(client);
+       return ret;
+}
+
+static const struct i2c_device_id bh1730_i2c_device_id[] = {
+       { BH1730_NAME, 0 },
+       {}
+};
+
+MODULE_DEVICE_TABLE(i2c, bh1730_i2c_device_id);
+
+static const struct of_device_id bh1730_of_match[] = {
+       { .compatible = "rohm,bh1730fvc", },
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, bh1730_of_match);
+
+static struct i2c_driver bh1730_driver = {
+       .class          = I2C_CLASS_HWMON,
+       .probe          = bh1730_probe,
+       .remove         = bh1730_remove,
+       .shutdown       = bh1730_shutdown,
+       .driver = {
+               .name           = BH1730_NAME,
+               .owner          = THIS_MODULE,
+               .of_match_table = of_match_ptr(bh1730_of_match),
+               .pm             = &bh1730_pm_ops,
+       },
+       .id_table       = bh1730_i2c_device_id,
+};
+module_i2c_driver(bh1730_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("BH1730FVC driver");
+MODULE_AUTHOR("NVIDIA Corporation");