linux/drivers/iio/light/al3010.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * AL3010 - Dyna Image Ambient Light Sensor
   4 *
   5 * Copyright (c) 2014, Intel Corporation.
   6 * Copyright (c) 2016, Dyna-Image Corp.
   7 * Copyright (c) 2020, David Heidelberg, Michał Mirosław, Dmitry Osipenko
   8 *
   9 * IIO driver for AL3010 (7-bit I2C slave address 0x1C).
  10 *
  11 * TODO: interrupt support, thresholds
  12 * When the driver will get support for interrupt handling, then interrupt
  13 * will need to be disabled before turning sensor OFF in order to avoid
  14 * potential races with the interrupt handling.
  15 */
  16
  17#include <linux/bitfield.h>
  18#include <linux/i2c.h>
  19#include <linux/module.h>
  20#include <linux/of.h>
  21
  22#include <linux/iio/iio.h>
  23#include <linux/iio/sysfs.h>
  24
  25#define AL3010_DRV_NAME "al3010"
  26
  27#define AL3010_REG_SYSTEM               0x00
  28#define AL3010_REG_DATA_LOW             0x0c
  29#define AL3010_REG_CONFIG               0x10
  30
  31#define AL3010_CONFIG_DISABLE           0x00
  32#define AL3010_CONFIG_ENABLE            0x01
  33
  34#define AL3010_GAIN_MASK                GENMASK(6,4)
  35
  36#define AL3010_SCALE_AVAILABLE "1.1872 0.2968 0.0742 0.018"
  37
  38enum al3xxxx_range {
  39        AL3XXX_RANGE_1, /* 77806 lx */
  40        AL3XXX_RANGE_2, /* 19542 lx */
  41        AL3XXX_RANGE_3, /*  4863 lx */
  42        AL3XXX_RANGE_4  /*  1216 lx */
  43};
  44
  45static const int al3010_scales[][2] = {
  46        {0, 1187200}, {0, 296800}, {0, 74200}, {0, 18600}
  47};
  48
  49struct al3010_data {
  50        struct i2c_client *client;
  51};
  52
  53static const struct iio_chan_spec al3010_channels[] = {
  54        {
  55                .type   = IIO_LIGHT,
  56                .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
  57                                      BIT(IIO_CHAN_INFO_SCALE),
  58        }
  59};
  60
  61static IIO_CONST_ATTR(in_illuminance_scale_available, AL3010_SCALE_AVAILABLE);
  62
  63static struct attribute *al3010_attributes[] = {
  64        &iio_const_attr_in_illuminance_scale_available.dev_attr.attr,
  65        NULL,
  66};
  67
  68static const struct attribute_group al3010_attribute_group = {
  69        .attrs = al3010_attributes,
  70};
  71
  72static int al3010_set_pwr(struct i2c_client *client, bool pwr)
  73{
  74        u8 val = pwr ? AL3010_CONFIG_ENABLE : AL3010_CONFIG_DISABLE;
  75        return i2c_smbus_write_byte_data(client, AL3010_REG_SYSTEM, val);
  76}
  77
  78static void al3010_set_pwr_off(void *_data)
  79{
  80        struct al3010_data *data = _data;
  81
  82        al3010_set_pwr(data->client, false);
  83}
  84
  85static int al3010_init(struct al3010_data *data)
  86{
  87        int ret;
  88
  89        ret = al3010_set_pwr(data->client, true);
  90
  91        if (ret < 0)
  92                return ret;
  93
  94        ret = i2c_smbus_write_byte_data(data->client, AL3010_REG_CONFIG,
  95                                        FIELD_PREP(AL3010_GAIN_MASK,
  96                                                   AL3XXX_RANGE_3));
  97        if (ret < 0)
  98                return ret;
  99
 100        return 0;
 101}
 102
 103static int al3010_read_raw(struct iio_dev *indio_dev,
 104                           struct iio_chan_spec const *chan, int *val,
 105                           int *val2, long mask)
 106{
 107        struct al3010_data *data = iio_priv(indio_dev);
 108        int ret;
 109
 110        switch (mask) {
 111        case IIO_CHAN_INFO_RAW:
 112                /*
 113                 * ALS ADC value is stored in two adjacent registers:
 114                 * - low byte of output is stored at AL3010_REG_DATA_LOW
 115                 * - high byte of output is stored at AL3010_REG_DATA_LOW + 1
 116                 */
 117                ret = i2c_smbus_read_word_data(data->client,
 118                                               AL3010_REG_DATA_LOW);
 119                if (ret < 0)
 120                        return ret;
 121                *val = ret;
 122                return IIO_VAL_INT;
 123        case IIO_CHAN_INFO_SCALE:
 124                ret = i2c_smbus_read_byte_data(data->client,
 125                                               AL3010_REG_CONFIG);
 126                if (ret < 0)
 127                        return ret;
 128
 129                ret = FIELD_GET(AL3010_GAIN_MASK, ret);
 130                *val = al3010_scales[ret][0];
 131                *val2 = al3010_scales[ret][1];
 132
 133                return IIO_VAL_INT_PLUS_MICRO;
 134        }
 135        return -EINVAL;
 136}
 137
 138static int al3010_write_raw(struct iio_dev *indio_dev,
 139                            struct iio_chan_spec const *chan, int val,
 140                            int val2, long mask)
 141{
 142        struct al3010_data *data = iio_priv(indio_dev);
 143        int i;
 144
 145        switch (mask) {
 146        case IIO_CHAN_INFO_SCALE:
 147                for (i = 0; i < ARRAY_SIZE(al3010_scales); i++) {
 148                        if (val != al3010_scales[i][0] ||
 149                            val2 != al3010_scales[i][1])
 150                                continue;
 151
 152                        return i2c_smbus_write_byte_data(data->client,
 153                                        AL3010_REG_CONFIG,
 154                                        FIELD_PREP(AL3010_GAIN_MASK, i));
 155                }
 156                break;
 157        }
 158        return -EINVAL;
 159}
 160
 161static const struct iio_info al3010_info = {
 162        .read_raw       = al3010_read_raw,
 163        .write_raw      = al3010_write_raw,
 164        .attrs          = &al3010_attribute_group,
 165};
 166
 167static int al3010_probe(struct i2c_client *client,
 168                        const struct i2c_device_id *id)
 169{
 170        struct al3010_data *data;
 171        struct iio_dev *indio_dev;
 172        int ret;
 173
 174        indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
 175        if (!indio_dev)
 176                return -ENOMEM;
 177
 178        data = iio_priv(indio_dev);
 179        i2c_set_clientdata(client, indio_dev);
 180        data->client = client;
 181
 182        indio_dev->info = &al3010_info;
 183        indio_dev->name = AL3010_DRV_NAME;
 184        indio_dev->channels = al3010_channels;
 185        indio_dev->num_channels = ARRAY_SIZE(al3010_channels);
 186        indio_dev->modes = INDIO_DIRECT_MODE;
 187
 188        ret = al3010_init(data);
 189        if (ret < 0) {
 190                dev_err(&client->dev, "al3010 chip init failed\n");
 191                return ret;
 192        }
 193
 194        ret = devm_add_action_or_reset(&client->dev,
 195                                        al3010_set_pwr_off,
 196                                        data);
 197        if (ret < 0)
 198                return ret;
 199
 200        return devm_iio_device_register(&client->dev, indio_dev);
 201}
 202
 203static int __maybe_unused al3010_suspend(struct device *dev)
 204{
 205        return al3010_set_pwr(to_i2c_client(dev), false);
 206}
 207
 208static int __maybe_unused al3010_resume(struct device *dev)
 209{
 210        return al3010_set_pwr(to_i2c_client(dev), true);
 211}
 212
 213static SIMPLE_DEV_PM_OPS(al3010_pm_ops, al3010_suspend, al3010_resume);
 214
 215static const struct i2c_device_id al3010_id[] = {
 216        {"al3010", },
 217        {}
 218};
 219MODULE_DEVICE_TABLE(i2c, al3010_id);
 220
 221static const struct of_device_id al3010_of_match[] = {
 222        { .compatible = "dynaimage,al3010", },
 223        {},
 224};
 225MODULE_DEVICE_TABLE(of, al3010_of_match);
 226
 227static struct i2c_driver al3010_driver = {
 228        .driver = {
 229                .name = AL3010_DRV_NAME,
 230                .of_match_table = al3010_of_match,
 231                .pm = &al3010_pm_ops,
 232        },
 233        .probe          = al3010_probe,
 234        .id_table       = al3010_id,
 235};
 236module_i2c_driver(al3010_driver);
 237
 238MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
 239MODULE_AUTHOR("David Heidelberg <david@ixit.cz>");
 240MODULE_DESCRIPTION("AL3010 Ambient Light Sensor driver");
 241MODULE_LICENSE("GPL v2");
 242