linux/drivers/iio/light/acpi-als.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * ACPI Ambient Light Sensor Driver
   4 *
   5 * Based on ALS driver:
   6 * Copyright (C) 2009 Zhang Rui <rui.zhang@intel.com>
   7 *
   8 * Rework for IIO subsystem:
   9 * Copyright (C) 2012-2013 Martin Liska <marxin.liska@gmail.com>
  10 *
  11 * Final cleanup and debugging:
  12 * Copyright (C) 2013-2014 Marek Vasut <marex@denx.de>
  13 * Copyright (C) 2015 Gabriele Mazzotta <gabriele.mzt@gmail.com>
  14 */
  15
  16#include <linux/module.h>
  17#include <linux/acpi.h>
  18#include <linux/err.h>
  19#include <linux/mutex.h>
  20
  21#include <linux/iio/iio.h>
  22#include <linux/iio/buffer.h>
  23#include <linux/iio/kfifo_buf.h>
  24
  25#define ACPI_ALS_CLASS                  "als"
  26#define ACPI_ALS_DEVICE_NAME            "acpi-als"
  27#define ACPI_ALS_NOTIFY_ILLUMINANCE     0x80
  28
  29ACPI_MODULE_NAME("acpi-als");
  30
  31/*
  32 * So far, there's only one channel in here, but the specification for
  33 * ACPI0008 says there can be more to what the block can report. Like
  34 * chromaticity and such. We are ready for incoming additions!
  35 */
  36static const struct iio_chan_spec acpi_als_channels[] = {
  37        {
  38                .type           = IIO_LIGHT,
  39                .scan_type      = {
  40                        .sign           = 's',
  41                        .realbits       = 32,
  42                        .storagebits    = 32,
  43                },
  44                /* _RAW is here for backward ABI compatibility */
  45                .info_mask_separate     = BIT(IIO_CHAN_INFO_RAW) |
  46                                          BIT(IIO_CHAN_INFO_PROCESSED),
  47        },
  48};
  49
  50/*
  51 * The event buffer contains timestamp and all the data from
  52 * the ACPI0008 block. There are multiple, but so far we only
  53 * support _ALI (illuminance). Once someone adds new channels
  54 * to acpi_als_channels[], the evt_buffer below will grow
  55 * automatically.
  56 */
  57#define ACPI_ALS_EVT_NR_SOURCES         ARRAY_SIZE(acpi_als_channels)
  58#define ACPI_ALS_EVT_BUFFER_SIZE                \
  59        (sizeof(s64) + (ACPI_ALS_EVT_NR_SOURCES * sizeof(s32)))
  60
  61struct acpi_als {
  62        struct acpi_device      *device;
  63        struct mutex            lock;
  64
  65        s32                     evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE];
  66};
  67
  68/*
  69 * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT
  70 * and ALP can all be handled by acpi_als_read_value() below, while the ALR is
  71 * special.
  72 *
  73 * The _ALR property returns tables that can be used to fine-tune the values
  74 * reported by the other props based on the particular hardware type and it's
  75 * location (it contains tables for "rainy", "bright inhouse lighting" etc.).
  76 *
  77 * So far, we support only ALI (illuminance).
  78 */
  79#define ACPI_ALS_ILLUMINANCE    "_ALI"
  80#define ACPI_ALS_CHROMATICITY   "_ALC"
  81#define ACPI_ALS_COLOR_TEMP     "_ALT"
  82#define ACPI_ALS_POLLING        "_ALP"
  83#define ACPI_ALS_TABLES         "_ALR"
  84
  85static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val)
  86{
  87        unsigned long long temp_val;
  88        acpi_status status;
  89
  90        status = acpi_evaluate_integer(als->device->handle, prop, NULL,
  91                                       &temp_val);
  92
  93        if (ACPI_FAILURE(status)) {
  94                ACPI_EXCEPTION((AE_INFO, status, "Error reading ALS %s", prop));
  95                return -EIO;
  96        }
  97
  98        *val = temp_val;
  99
 100        return 0;
 101}
 102
 103static void acpi_als_notify(struct acpi_device *device, u32 event)
 104{
 105        struct iio_dev *indio_dev = acpi_driver_data(device);
 106        struct acpi_als *als = iio_priv(indio_dev);
 107        s32 *buffer = als->evt_buffer;
 108        s64 time_ns = iio_get_time_ns(indio_dev);
 109        s32 val;
 110        int ret;
 111
 112        mutex_lock(&als->lock);
 113
 114        memset(buffer, 0, ACPI_ALS_EVT_BUFFER_SIZE);
 115
 116        switch (event) {
 117        case ACPI_ALS_NOTIFY_ILLUMINANCE:
 118                ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val);
 119                if (ret < 0)
 120                        goto out;
 121                *buffer++ = val;
 122                break;
 123        default:
 124                /* Unhandled event */
 125                dev_dbg(&device->dev, "Unhandled ACPI ALS event (%08x)!\n",
 126                        event);
 127                goto out;
 128        }
 129
 130        iio_push_to_buffers_with_timestamp(indio_dev, als->evt_buffer, time_ns);
 131
 132out:
 133        mutex_unlock(&als->lock);
 134}
 135
 136static int acpi_als_read_raw(struct iio_dev *indio_dev,
 137                             struct iio_chan_spec const *chan, int *val,
 138                             int *val2, long mask)
 139{
 140        struct acpi_als *als = iio_priv(indio_dev);
 141        s32 temp_val;
 142        int ret;
 143
 144        if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW))
 145                return -EINVAL;
 146
 147        /* we support only illumination (_ALI) so far. */
 148        if (chan->type != IIO_LIGHT)
 149                return -EINVAL;
 150
 151        ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val);
 152        if (ret < 0)
 153                return ret;
 154
 155        *val = temp_val;
 156
 157        return IIO_VAL_INT;
 158}
 159
 160static const struct iio_info acpi_als_info = {
 161        .read_raw               = acpi_als_read_raw,
 162};
 163
 164static int acpi_als_add(struct acpi_device *device)
 165{
 166        struct acpi_als *als;
 167        struct iio_dev *indio_dev;
 168        struct iio_buffer *buffer;
 169
 170        indio_dev = devm_iio_device_alloc(&device->dev, sizeof(*als));
 171        if (!indio_dev)
 172                return -ENOMEM;
 173
 174        als = iio_priv(indio_dev);
 175
 176        device->driver_data = indio_dev;
 177        als->device = device;
 178        mutex_init(&als->lock);
 179
 180        indio_dev->name = ACPI_ALS_DEVICE_NAME;
 181        indio_dev->info = &acpi_als_info;
 182        indio_dev->modes = INDIO_BUFFER_SOFTWARE;
 183        indio_dev->channels = acpi_als_channels;
 184        indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels);
 185
 186        buffer = devm_iio_kfifo_allocate(&device->dev);
 187        if (!buffer)
 188                return -ENOMEM;
 189
 190        iio_device_attach_buffer(indio_dev, buffer);
 191
 192        return devm_iio_device_register(&device->dev, indio_dev);
 193}
 194
 195static const struct acpi_device_id acpi_als_device_ids[] = {
 196        {"ACPI0008", 0},
 197        {},
 198};
 199
 200MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
 201
 202static struct acpi_driver acpi_als_driver = {
 203        .name   = "acpi_als",
 204        .class  = ACPI_ALS_CLASS,
 205        .ids    = acpi_als_device_ids,
 206        .ops = {
 207                .add    = acpi_als_add,
 208                .notify = acpi_als_notify,
 209        },
 210};
 211
 212module_acpi_driver(acpi_als_driver);
 213
 214MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
 215MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>");
 216MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
 217MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver");
 218MODULE_LICENSE("GPL");
 219