root/drivers/iio/light/bh1780.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * ROHM 1780GLI Ambient Light Sensor Driver
 *
 * Copyright (C) 2016 Linaro Ltd.
 * Author: Linus Walleij <linus.walleij@linaro.org>
 * Loosely based on the previous BH1780 ALS misc driver
 * Copyright (C) 2010 Texas Instruments
 * Author: Hemanth V <hemanthv@ti.com>
 */
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
#include <linux/bitops.h>

#define BH1780_CMD_BIT          BIT(7)
#define BH1780_REG_CONTROL      0x00
#define BH1780_REG_PARTID       0x0A
#define BH1780_REG_MANFID       0x0B
#define BH1780_REG_DLOW         0x0C
#define BH1780_REG_DHIGH        0x0D

#define BH1780_REVMASK          GENMASK(3,0)
#define BH1780_POWMASK          GENMASK(1,0)
#define BH1780_POFF             (0x0)
#define BH1780_PON              (0x3)

/* power on settling time in ms */
#define BH1780_PON_DELAY        2
/* max time before value available in ms */
#define BH1780_INTERVAL         250

struct bh1780_data {
        struct i2c_client *client;
};

static int bh1780_write(struct bh1780_data *bh1780, u8 reg, u8 val)
{
        int ret = i2c_smbus_write_byte_data(bh1780->client,
                                            BH1780_CMD_BIT | reg,
                                            val);
        if (ret < 0)
                dev_err(&bh1780->client->dev,
                        "i2c_smbus_write_byte_data failed error "
                        "%d, register %01x\n",
                        ret, reg);
        return ret;
}

static int bh1780_read(struct bh1780_data *bh1780, u8 reg)
{
        int ret = i2c_smbus_read_byte_data(bh1780->client,
                                           BH1780_CMD_BIT | reg);
        if (ret < 0)
                dev_err(&bh1780->client->dev,
                        "i2c_smbus_read_byte_data failed error "
                        "%d, register %01x\n",
                        ret, reg);
        return ret;
}

static int bh1780_read_word(struct bh1780_data *bh1780, u8 reg)
{
        int ret = i2c_smbus_read_word_data(bh1780->client,
                                           BH1780_CMD_BIT | reg);
        if (ret < 0)
                dev_err(&bh1780->client->dev,
                        "i2c_smbus_read_word_data failed error "
                        "%d, register %01x\n",
                        ret, reg);
        return ret;
}

static int bh1780_debugfs_reg_access(struct iio_dev *indio_dev,
                              unsigned int reg, unsigned int writeval,
                              unsigned int *readval)
{
        struct bh1780_data *bh1780 = iio_priv(indio_dev);
        int ret;

        if (!readval)
                return bh1780_write(bh1780, (u8)reg, (u8)writeval);

        ret = bh1780_read(bh1780, (u8)reg);
        if (ret < 0)
                return ret;

        *readval = ret;

        return 0;
}

static int bh1780_read_raw(struct iio_dev *indio_dev,
                           struct iio_chan_spec const *chan,
                           int *val, int *val2, long mask)
{
        struct bh1780_data *bh1780 = iio_priv(indio_dev);
        int value;

        switch (mask) {
        case IIO_CHAN_INFO_RAW:
                switch (chan->type) {
                case IIO_LIGHT:
                        pm_runtime_get_sync(&bh1780->client->dev);
                        value = bh1780_read_word(bh1780, BH1780_REG_DLOW);
                        pm_runtime_put_autosuspend(&bh1780->client->dev);
                        if (value < 0)
                                return value;
                        *val = value;

                        return IIO_VAL_INT;
                default:
                        return -EINVAL;
                }
        case IIO_CHAN_INFO_INT_TIME:
                *val = 0;
                *val2 = BH1780_INTERVAL * 1000;
                return IIO_VAL_INT_PLUS_MICRO;
        default:
                return -EINVAL;
        }
}

static const struct iio_info bh1780_info = {
        .read_raw = bh1780_read_raw,
        .debugfs_reg_access = bh1780_debugfs_reg_access,
};

static const struct iio_chan_spec bh1780_channels[] = {
        {
                .type = IIO_LIGHT,
                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
                                      BIT(IIO_CHAN_INFO_INT_TIME)
        }
};

static int bh1780_probe(struct i2c_client *client)
{
        int ret;
        struct bh1780_data *bh1780;
        struct i2c_adapter *adapter = client->adapter;
        struct iio_dev *indio_dev;

        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
                return -EIO;

        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*bh1780));
        if (!indio_dev)
                return -ENOMEM;

        bh1780 = iio_priv(indio_dev);
        bh1780->client = client;
        i2c_set_clientdata(client, indio_dev);

        /* Power up the device */
        ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON);
        if (ret < 0)
                return ret;
        msleep(BH1780_PON_DELAY);
        pm_runtime_get_noresume(&client->dev);
        pm_runtime_set_active(&client->dev);
        pm_runtime_enable(&client->dev);

        ret = bh1780_read(bh1780, BH1780_REG_PARTID);
        if (ret < 0)
                goto out_disable_pm;
        dev_info(&client->dev,
                 "Ambient Light Sensor, Rev : %lu\n",
                 (ret & BH1780_REVMASK));

        /*
         * As the device takes 250 ms to even come up with a fresh
         * measurement after power-on, do not shut it down unnecessarily.
         * Set autosuspend to a five seconds.
         */
        pm_runtime_set_autosuspend_delay(&client->dev, 5000);
        pm_runtime_use_autosuspend(&client->dev);
        pm_runtime_put(&client->dev);

        indio_dev->info = &bh1780_info;
        indio_dev->name = "bh1780";
        indio_dev->channels = bh1780_channels;
        indio_dev->num_channels = ARRAY_SIZE(bh1780_channels);
        indio_dev->modes = INDIO_DIRECT_MODE;

        ret = iio_device_register(indio_dev);
        if (ret)
                goto out_disable_pm;
        return 0;

out_disable_pm:
        pm_runtime_put_noidle(&client->dev);
        pm_runtime_disable(&client->dev);
        return ret;
}

static void bh1780_remove(struct i2c_client *client)
{
        struct iio_dev *indio_dev = i2c_get_clientdata(client);
        struct bh1780_data *bh1780 = iio_priv(indio_dev);
        int ret;

        iio_device_unregister(indio_dev);
        pm_runtime_get_sync(&client->dev);
        pm_runtime_put_noidle(&client->dev);
        pm_runtime_disable(&client->dev);
        ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF);
        if (ret < 0)
                dev_err(&client->dev, "failed to power off (%pe)\n",
                        ERR_PTR(ret));
}

static int bh1780_runtime_suspend(struct device *dev)
{
        struct i2c_client *client = to_i2c_client(dev);
        struct iio_dev *indio_dev = i2c_get_clientdata(client);
        struct bh1780_data *bh1780 = iio_priv(indio_dev);
        int ret;

        ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_POFF);
        if (ret < 0) {
                dev_err(dev, "failed to runtime suspend\n");
                return ret;
        }

        return 0;
}

static int bh1780_runtime_resume(struct device *dev)
{
        struct i2c_client *client = to_i2c_client(dev);
        struct iio_dev *indio_dev = i2c_get_clientdata(client);
        struct bh1780_data *bh1780 = iio_priv(indio_dev);
        int ret;

        ret = bh1780_write(bh1780, BH1780_REG_CONTROL, BH1780_PON);
        if (ret < 0) {
                dev_err(dev, "failed to runtime resume\n");
                return ret;
        }

        /* Wait for power on, then for a value to be available */
        msleep(BH1780_PON_DELAY + BH1780_INTERVAL);

        return 0;
}

static DEFINE_RUNTIME_DEV_PM_OPS(bh1780_dev_pm_ops, bh1780_runtime_suspend,
                                bh1780_runtime_resume, NULL);

static const struct i2c_device_id bh1780_id[] = {
        { .name = "bh1780" },
        { }
};

MODULE_DEVICE_TABLE(i2c, bh1780_id);

static const struct of_device_id of_bh1780_match[] = {
        { .compatible = "rohm,bh1780gli", },
        { }
};
MODULE_DEVICE_TABLE(of, of_bh1780_match);

static struct i2c_driver bh1780_driver = {
        .probe          = bh1780_probe,
        .remove         = bh1780_remove,
        .id_table       = bh1780_id,
        .driver = {
                .name = "bh1780",
                .pm = pm_ptr(&bh1780_dev_pm_ops),
                .of_match_table = of_bh1780_match,
        },
};

module_i2c_driver(bh1780_driver);

MODULE_DESCRIPTION("ROHM BH1780GLI Ambient Light Sensor Driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");