File Index
Files and directories
Sign in
FILE

acpi-als.c

File preview with syntax highlighting.

linux-6.19.14/drivers/iio/light/acpi-als.c C
  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/irq.h>
 20#include <linux/mutex.h>
 21
 22#include <linux/iio/iio.h>
 23#include <linux/iio/buffer.h>
 24#include <linux/iio/trigger.h>
 25#include <linux/iio/triggered_buffer.h>
 26#include <linux/iio/trigger_consumer.h>
 27
 28#define ACPI_ALS_CLASS			"als"
 29#define ACPI_ALS_DEVICE_NAME		"acpi-als"
 30#define ACPI_ALS_NOTIFY_ILLUMINANCE	0x80
 31
 32/*
 33 * So far, there's only one channel in here, but the specification for
 34 * ACPI0008 says there can be more to what the block can report. Like
 35 * chromaticity and such. We are ready for incoming additions!
 36 */
 37static const struct iio_chan_spec acpi_als_channels[] = {
 38	{
 39		.type		= IIO_LIGHT,
 40		.scan_type	= {
 41			.sign		= 's',
 42			.realbits	= 32,
 43			.storagebits	= 32,
 44		},
 45		/* _RAW is here for backward ABI compatibility */
 46		.info_mask_separate	= BIT(IIO_CHAN_INFO_RAW) |
 47					  BIT(IIO_CHAN_INFO_PROCESSED),
 48	},
 49	IIO_CHAN_SOFT_TIMESTAMP(1),
 50};
 51
 52struct acpi_als {
 53	struct acpi_device	*device;
 54	struct mutex		lock;
 55	struct iio_trigger	*trig;
 56};
 57
 58/*
 59 * All types of properties the ACPI0008 block can report. The ALI, ALC, ALT
 60 * and ALP can all be handled by acpi_als_read_value() below, while the ALR is
 61 * special.
 62 *
 63 * The _ALR property returns tables that can be used to fine-tune the values
 64 * reported by the other props based on the particular hardware type and it's
 65 * location (it contains tables for "rainy", "bright inhouse lighting" etc.).
 66 *
 67 * So far, we support only ALI (illuminance).
 68 */
 69#define ACPI_ALS_ILLUMINANCE	"_ALI"
 70#define ACPI_ALS_CHROMATICITY	"_ALC"
 71#define ACPI_ALS_COLOR_TEMP	"_ALT"
 72#define ACPI_ALS_POLLING	"_ALP"
 73#define ACPI_ALS_TABLES		"_ALR"
 74
 75static int acpi_als_read_value(struct acpi_als *als, char *prop, s32 *val)
 76{
 77	unsigned long long temp_val;
 78	acpi_status status;
 79
 80	status = acpi_evaluate_integer(als->device->handle, prop, NULL,
 81				       &temp_val);
 82
 83	if (ACPI_FAILURE(status)) {
 84		acpi_evaluation_failure_warn(als->device->handle, prop, status);
 85		return -EIO;
 86	}
 87
 88	*val = temp_val;
 89
 90	return 0;
 91}
 92
 93static void acpi_als_notify(struct acpi_device *device, u32 event)
 94{
 95	struct iio_dev *indio_dev = acpi_driver_data(device);
 96	struct acpi_als *als = iio_priv(indio_dev);
 97
 98	if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev)) {
 99		switch (event) {
100		case ACPI_ALS_NOTIFY_ILLUMINANCE:
101			iio_trigger_poll_nested(als->trig);
102			break;
103		default:
104			/* Unhandled event */
105			dev_dbg(&device->dev,
106				"Unhandled ACPI ALS event (%08x)!\n",
107				event);
108		}
109	}
110}
111
112static int acpi_als_read_raw(struct iio_dev *indio_dev,
113			     struct iio_chan_spec const *chan, int *val,
114			     int *val2, long mask)
115{
116	struct acpi_als *als = iio_priv(indio_dev);
117	s32 temp_val;
118	int ret;
119
120	if ((mask != IIO_CHAN_INFO_PROCESSED) && (mask != IIO_CHAN_INFO_RAW))
121		return -EINVAL;
122
123	/* we support only illumination (_ALI) so far. */
124	if (chan->type != IIO_LIGHT)
125		return -EINVAL;
126
127	ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &temp_val);
128	if (ret < 0)
129		return ret;
130
131	*val = temp_val;
132
133	return IIO_VAL_INT;
134}
135
136static const struct iio_info acpi_als_info = {
137	.read_raw		= acpi_als_read_raw,
138};
139
140static irqreturn_t acpi_als_trigger_handler(int irq, void *p)
141{
142	struct iio_poll_func *pf = p;
143	struct iio_dev *indio_dev = pf->indio_dev;
144	struct acpi_als *als = iio_priv(indio_dev);
145	struct {
146		s32 light;
147		aligned_s64 ts;
148	} scan = { };
149	s32 val;
150	int ret;
151
152	mutex_lock(&als->lock);
153
154	ret = acpi_als_read_value(als, ACPI_ALS_ILLUMINANCE, &val);
155	if (ret < 0)
156		goto out;
157	scan.light = val;
158
159	/*
160	 * When coming from own trigger via polls, set polling function
161	 * timestamp here. Given ACPI notifier is already in a thread and call
162	 * function directly, there is no need to set the timestamp in the
163	 * notify function.
164	 *
165	 * If the timestamp was actually 0, the timestamp is set one more time.
166	 */
167	if (!pf->timestamp)
168		pf->timestamp = iio_get_time_ns(indio_dev);
169
170	iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), pf->timestamp);
171out:
172	mutex_unlock(&als->lock);
173	iio_trigger_notify_done(indio_dev->trig);
174
175	return IRQ_HANDLED;
176}
177
178static int acpi_als_add(struct acpi_device *device)
179{
180	struct device *dev = &device->dev;
181	struct iio_dev *indio_dev;
182	struct acpi_als *als;
183	int ret;
184
185	indio_dev = devm_iio_device_alloc(dev, sizeof(*als));
186	if (!indio_dev)
187		return -ENOMEM;
188
189	als = iio_priv(indio_dev);
190
191	device->driver_data = indio_dev;
192	als->device = device;
193	mutex_init(&als->lock);
194
195	indio_dev->name = ACPI_ALS_DEVICE_NAME;
196	indio_dev->info = &acpi_als_info;
197	indio_dev->channels = acpi_als_channels;
198	indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels);
199
200	als->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
201					   iio_device_id(indio_dev));
202	if (!als->trig)
203		return -ENOMEM;
204
205	ret = devm_iio_trigger_register(dev, als->trig);
206	if (ret)
207		return ret;
208	/*
209	 * Set hardware trigger by default to let events flow when
210	 * BIOS support notification.
211	 */
212	indio_dev->trig = iio_trigger_get(als->trig);
213
214	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
215					      iio_pollfunc_store_time,
216					      acpi_als_trigger_handler,
217					      NULL);
218	if (ret)
219		return ret;
220
221	return devm_iio_device_register(dev, indio_dev);
222}
223
224static const struct acpi_device_id acpi_als_device_ids[] = {
225	{"ACPI0008", 0},
226	{ }
227};
228
229MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
230
231static struct acpi_driver acpi_als_driver = {
232	.name	= "acpi_als",
233	.class	= ACPI_ALS_CLASS,
234	.ids	= acpi_als_device_ids,
235	.ops = {
236		.add	= acpi_als_add,
237		.notify	= acpi_als_notify,
238	},
239};
240
241module_acpi_driver(acpi_als_driver);
242
243MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
244MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>");
245MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
246MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver");
247MODULE_LICENSE("GPL");
~/ Read-only file preview.