xref: /linux/drivers/iio/light/acpi-als.c (revision 6093a688a07da07808f0122f9aa2a3eed250d853)
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  */
37 static 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 
52 struct 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 
75 static 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 
93 static 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 
112 static 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 
136 static const struct iio_info acpi_als_info = {
137 	.read_raw		= acpi_als_read_raw,
138 };
139 
140 static 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);
171 out:
172 	mutex_unlock(&als->lock);
173 	iio_trigger_notify_done(indio_dev->trig);
174 
175 	return IRQ_HANDLED;
176 }
177 
178 static 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 
224 static const struct acpi_device_id acpi_als_device_ids[] = {
225 	{"ACPI0008", 0},
226 	{ }
227 };
228 
229 MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
230 
231 static 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 
241 module_acpi_driver(acpi_als_driver);
242 
243 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
244 MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>");
245 MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
246 MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver");
247 MODULE_LICENSE("GPL");
248