| /* | |
| * 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/of.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); | |
| if (value < 0) | |
| return value; | |
| pm_runtime_mark_last_busy(&bh1780->client->dev); | |
| pm_runtime_put_autosuspend(&bh1780->client->dev); | |
| *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 = { | |
| .driver_module = THIS_MODULE, | |
| .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, | |
| const struct i2c_device_id *id) | |
| { | |
| int ret; | |
| struct bh1780_data *bh1780; | |
| struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); | |
| 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->dev.parent = &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 int 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\n"); | |
| return ret; | |
| } | |
| return 0; | |
| } | |
| #ifdef CONFIG_PM | |
| 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; | |
| } | |
| #endif /* CONFIG_PM */ | |
| static const struct dev_pm_ops bh1780_dev_pm_ops = { | |
| SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, | |
| pm_runtime_force_resume) | |
| SET_RUNTIME_PM_OPS(bh1780_runtime_suspend, | |
| bh1780_runtime_resume, NULL) | |
| }; | |
| static const struct i2c_device_id bh1780_id[] = { | |
| { "bh1780", 0 }, | |
| { }, | |
| }; | |
| MODULE_DEVICE_TABLE(i2c, bh1780_id); | |
| #ifdef CONFIG_OF | |
| static const struct of_device_id of_bh1780_match[] = { | |
| { .compatible = "rohm,bh1780gli", }, | |
| {}, | |
| }; | |
| MODULE_DEVICE_TABLE(of, of_bh1780_match); | |
| #endif | |
| static struct i2c_driver bh1780_driver = { | |
| .probe = bh1780_probe, | |
| .remove = bh1780_remove, | |
| .id_table = bh1780_id, | |
| .driver = { | |
| .name = "bh1780", | |
| .pm = &bh1780_dev_pm_ops, | |
| .of_match_table = of_match_ptr(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>"); |