162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ACPI Ambient Light Sensor Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Based on ALS driver: 662306a36Sopenharmony_ci * Copyright (C) 2009 Zhang Rui <rui.zhang@intel.com> 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Rework for IIO subsystem: 962306a36Sopenharmony_ci * Copyright (C) 2012-2013 Martin Liska <marxin.liska@gmail.com> 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Final cleanup and debugging: 1262306a36Sopenharmony_ci * Copyright (C) 2013-2014 Marek Vasut <marex@denx.de> 1362306a36Sopenharmony_ci * Copyright (C) 2015 Gabriele Mazzotta <gabriele.mzt@gmail.com> 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/acpi.h> 1862306a36Sopenharmony_ci#include <linux/err.h> 1962306a36Sopenharmony_ci#include <linux/irq.h> 2062306a36Sopenharmony_ci#include <linux/mutex.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <linux/iio/iio.h> 2362306a36Sopenharmony_ci#include <linux/iio/buffer.h> 2462306a36Sopenharmony_ci#include <linux/iio/trigger.h> 2562306a36Sopenharmony_ci#include <linux/iio/triggered_buffer.h> 2662306a36Sopenharmony_ci#include <linux/iio/trigger_consumer.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#define ACPI_ALS_CLASS "als" 2962306a36Sopenharmony_ci#define ACPI_ALS_DEVICE_NAME "acpi-als" 3062306a36Sopenharmony_ci#define ACPI_ALS_NOTIFY_ILLUMINANCE 0x80 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci/* 3362306a36Sopenharmony_ci * So far, there's only one channel in here, but the specification for 3462306a36Sopenharmony_ci * ACPI0008 says there can be more to what the block can report. Like 3562306a36Sopenharmony_ci * chromaticity and such. We are ready for incoming additions! 3662306a36Sopenharmony_ci */ 3762306a36Sopenharmony_cistatic const struct iio_chan_spec acpi_als_channels[] = { 3862306a36Sopenharmony_ci { 3962306a36Sopenharmony_ci .type = IIO_LIGHT, 4062306a36Sopenharmony_ci .scan_type = { 4162306a36Sopenharmony_ci .sign = 's', 4262306a36Sopenharmony_ci .realbits = 32, 4362306a36Sopenharmony_ci .storagebits = 32, 4462306a36Sopenharmony_ci }, 4562306a36Sopenharmony_ci /* _RAW is here for backward ABI compatibility */ 4662306a36Sopenharmony_ci .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | 4762306a36Sopenharmony_ci BIT(IIO_CHAN_INFO_PROCESSED), 4862306a36Sopenharmony_ci }, 4962306a36Sopenharmony_ci IIO_CHAN_SOFT_TIMESTAMP(1), 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci/* 5362306a36Sopenharmony_ci * The event buffer contains timestamp and all the data from 5462306a36Sopenharmony_ci * the ACPI0008 block. There are multiple, but so far we only 5562306a36Sopenharmony_ci * support _ALI (illuminance): One channel, padding and timestamp. 5662306a36Sopenharmony_ci */ 5762306a36Sopenharmony_ci#define ACPI_ALS_EVT_BUFFER_SIZE \ 5862306a36Sopenharmony_ci (sizeof(s32) + sizeof(s32) + sizeof(s64)) 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistruct acpi_als { 6162306a36Sopenharmony_ci struct acpi_device *device; 6262306a36Sopenharmony_ci struct mutex lock; 6362306a36Sopenharmony_ci struct iio_trigger *trig; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci s32 evt_buffer[ACPI_ALS_EVT_BUFFER_SIZE / sizeof(s32)] __aligned(8); 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* 6962306a36Sopenharmony_ci * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT 7062306a36Sopenharmony_ci * and ALP can all be handled by acpi_als_read_value() below, while the ALR is 7162306a36Sopenharmony_ci * special. 7262306a36Sopenharmony_ci * 7362306a36Sopenharmony_ci * The _ALR property returns tables that can be used to fine-tune the values 7462306a36Sopenharmony_ci * reported by the other props based on the particular hardware type and it's 7562306a36Sopenharmony_ci * location (it contains tables for "rainy", "bright inhouse lighting" etc.). 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * So far, we support only ALI (illuminance). 7862306a36Sopenharmony_ci */ 7962306a36Sopenharmony_ci#define ACPI_ALS_ILLUMINANCE "_ALI" 8062306a36Sopenharmony_ci#define ACPI_ALS_CHROMATICITY "_ALC" 8162306a36Sopenharmony_ci#define ACPI_ALS_COLOR_TEMP "_ALT" 8262306a36Sopenharmony_ci#define ACPI_ALS_POLLING "_ALP" 8362306a36Sopenharmony_ci#define ACPI_ALS_TABLES "_ALR" 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci unsigned long long temp_val; 8862306a36Sopenharmony_ci acpi_status status; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci status = acpi_evaluate_integer(als->device->handle, prop, NULL, 9162306a36Sopenharmony_ci &temp_val); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 9462306a36Sopenharmony_ci acpi_evaluation_failure_warn(als->device->handle, prop, status); 9562306a36Sopenharmony_ci return -EIO; 9662306a36Sopenharmony_ci } 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci *val = temp_val; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci return 0; 10162306a36Sopenharmony_ci} 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_cistatic void acpi_als_notify(struct acpi_device *device, u32 event) 10462306a36Sopenharmony_ci{ 10562306a36Sopenharmony_ci struct iio_dev *indio_dev = acpi_driver_data(device); 10662306a36Sopenharmony_ci struct acpi_als *als = iio_priv(indio_dev); 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) { 10962306a36Sopenharmony_ci switch (event) { 11062306a36Sopenharmony_ci case ACPI_ALS_NOTIFY_ILLUMINANCE: 11162306a36Sopenharmony_ci iio_trigger_poll_nested(als->trig); 11262306a36Sopenharmony_ci break; 11362306a36Sopenharmony_ci default: 11462306a36Sopenharmony_ci /* Unhandled event */ 11562306a36Sopenharmony_ci dev_dbg(&device->dev, 11662306a36Sopenharmony_ci "Unhandled ACPI ALS event (%08x)!\n", 11762306a36Sopenharmony_ci event); 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci} 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_cistatic int acpi_als_read_raw(struct iio_dev *indio_dev, 12362306a36Sopenharmony_ci struct iio_chan_spec const *chan, int *val, 12462306a36Sopenharmony_ci int *val2, long mask) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct acpi_als *als = iio_priv(indio_dev); 12762306a36Sopenharmony_ci s32 temp_val; 12862306a36Sopenharmony_ci int ret; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW)) 13162306a36Sopenharmony_ci return -EINVAL; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* we support only illumination (_ALI) so far. */ 13462306a36Sopenharmony_ci if (chan->type != IIO_LIGHT) 13562306a36Sopenharmony_ci return -EINVAL; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val); 13862306a36Sopenharmony_ci if (ret < 0) 13962306a36Sopenharmony_ci return ret; 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_ci *val = temp_val; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci return IIO_VAL_INT; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic const struct iio_info acpi_als_info = { 14762306a36Sopenharmony_ci .read_raw = acpi_als_read_raw, 14862306a36Sopenharmony_ci}; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_cistatic irqreturn_t acpi_als_trigger_handler(int irq, void *p) 15162306a36Sopenharmony_ci{ 15262306a36Sopenharmony_ci struct iio_poll_func *pf = p; 15362306a36Sopenharmony_ci struct iio_dev *indio_dev = pf->indio_dev; 15462306a36Sopenharmony_ci struct acpi_als *als = iio_priv(indio_dev); 15562306a36Sopenharmony_ci s32 *buffer = als->evt_buffer; 15662306a36Sopenharmony_ci s32 val; 15762306a36Sopenharmony_ci int ret; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci mutex_lock(&als->lock); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val); 16262306a36Sopenharmony_ci if (ret < 0) 16362306a36Sopenharmony_ci goto out; 16462306a36Sopenharmony_ci *buffer = val; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* 16762306a36Sopenharmony_ci * When coming from own trigger via polls, set polling function 16862306a36Sopenharmony_ci * timestamp here. Given ACPI notifier is already in a thread and call 16962306a36Sopenharmony_ci * function directly, there is no need to set the timestamp in the 17062306a36Sopenharmony_ci * notify function. 17162306a36Sopenharmony_ci * 17262306a36Sopenharmony_ci * If the timestamp was actually 0, the timestamp is set one more time. 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_ci if (!pf->timestamp) 17562306a36Sopenharmony_ci pf->timestamp = iio_get_time_ns(indio_dev); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci iio_push_to_buffers_with_timestamp(indio_dev, buffer, pf->timestamp); 17862306a36Sopenharmony_ciout: 17962306a36Sopenharmony_ci mutex_unlock(&als->lock); 18062306a36Sopenharmony_ci iio_trigger_notify_done(indio_dev->trig); 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci return IRQ_HANDLED; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic int acpi_als_add(struct acpi_device *device) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct device *dev = &device->dev; 18862306a36Sopenharmony_ci struct iio_dev *indio_dev; 18962306a36Sopenharmony_ci struct acpi_als *als; 19062306a36Sopenharmony_ci int ret; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci indio_dev = devm_iio_device_alloc(dev, sizeof(*als)); 19362306a36Sopenharmony_ci if (!indio_dev) 19462306a36Sopenharmony_ci return -ENOMEM; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci als = iio_priv(indio_dev); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci device->driver_data = indio_dev; 19962306a36Sopenharmony_ci als->device = device; 20062306a36Sopenharmony_ci mutex_init(&als->lock); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci indio_dev->name = ACPI_ALS_DEVICE_NAME; 20362306a36Sopenharmony_ci indio_dev->info = &acpi_als_info; 20462306a36Sopenharmony_ci indio_dev->channels = acpi_als_channels; 20562306a36Sopenharmony_ci indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci als->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, 20862306a36Sopenharmony_ci iio_device_id(indio_dev)); 20962306a36Sopenharmony_ci if (!als->trig) 21062306a36Sopenharmony_ci return -ENOMEM; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci ret = devm_iio_trigger_register(dev, als->trig); 21362306a36Sopenharmony_ci if (ret) 21462306a36Sopenharmony_ci return ret; 21562306a36Sopenharmony_ci /* 21662306a36Sopenharmony_ci * Set hardware trigger by default to let events flow when 21762306a36Sopenharmony_ci * BIOS support notification. 21862306a36Sopenharmony_ci */ 21962306a36Sopenharmony_ci indio_dev->trig = iio_trigger_get(als->trig); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci ret = devm_iio_triggered_buffer_setup(dev, indio_dev, 22262306a36Sopenharmony_ci iio_pollfunc_store_time, 22362306a36Sopenharmony_ci acpi_als_trigger_handler, 22462306a36Sopenharmony_ci NULL); 22562306a36Sopenharmony_ci if (ret) 22662306a36Sopenharmony_ci return ret; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci return devm_iio_device_register(dev, indio_dev); 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic const struct acpi_device_id acpi_als_device_ids[] = { 23262306a36Sopenharmony_ci {"ACPI0008", 0}, 23362306a36Sopenharmony_ci {}, 23462306a36Sopenharmony_ci}; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, acpi_als_device_ids); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_cistatic struct acpi_driver acpi_als_driver = { 23962306a36Sopenharmony_ci .name = "acpi_als", 24062306a36Sopenharmony_ci .class = ACPI_ALS_CLASS, 24162306a36Sopenharmony_ci .ids = acpi_als_device_ids, 24262306a36Sopenharmony_ci .ops = { 24362306a36Sopenharmony_ci .add = acpi_als_add, 24462306a36Sopenharmony_ci .notify = acpi_als_notify, 24562306a36Sopenharmony_ci }, 24662306a36Sopenharmony_ci}; 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_cimodule_acpi_driver(acpi_als_driver); 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ciMODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); 25162306a36Sopenharmony_ciMODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>"); 25262306a36Sopenharmony_ciMODULE_AUTHOR("Marek Vasut <marex@denx.de>"); 25362306a36Sopenharmony_ciMODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver"); 25462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 255