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