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
acpi_als_read_value(struct acpi_als * als,char * prop,s32 * val)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
acpi_als_notify(acpi_handle handle,u32 event,void * data)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
acpi_als_read_raw(struct iio_dev * indio_dev,struct iio_chan_spec const * chan,int * val,int * val2,long mask)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
acpi_als_trigger_handler(int irq,void * p)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
acpi_als_probe(struct platform_device * pdev)179 static int acpi_als_probe(struct platform_device *pdev)
180 {
181 struct device *dev = &pdev->dev;
182 struct acpi_device *device = ACPI_COMPANION(dev);
183 struct iio_dev *indio_dev;
184 struct acpi_als *als;
185 int ret;
186
187 indio_dev = devm_iio_device_alloc(dev, sizeof(*als));
188 if (!indio_dev)
189 return -ENOMEM;
190
191 als = iio_priv(indio_dev);
192
193 als->device = device;
194 mutex_init(&als->lock);
195
196 indio_dev->name = ACPI_ALS_DEVICE_NAME;
197 indio_dev->info = &acpi_als_info;
198 indio_dev->channels = acpi_als_channels;
199 indio_dev->num_channels = ARRAY_SIZE(acpi_als_channels);
200
201 als->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
202 iio_device_id(indio_dev));
203 if (!als->trig)
204 return -ENOMEM;
205
206 ret = devm_iio_trigger_register(dev, als->trig);
207 if (ret)
208 return ret;
209 /*
210 * Set hardware trigger by default to let events flow when
211 * BIOS support notification.
212 */
213 indio_dev->trig = iio_trigger_get(als->trig);
214
215 ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
216 iio_pollfunc_store_time,
217 acpi_als_trigger_handler,
218 NULL);
219 if (ret)
220 return ret;
221
222 ret = devm_iio_device_register(dev, indio_dev);
223 if (ret)
224 return ret;
225
226 return acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
227 acpi_als_notify, indio_dev);
228 }
229
acpi_als_remove(struct platform_device * pdev)230 static void acpi_als_remove(struct platform_device *pdev)
231 {
232 acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev),
233 ACPI_DEVICE_NOTIFY, acpi_als_notify);
234 }
235
236 static const struct acpi_device_id acpi_als_device_ids[] = {
237 {"ACPI0008", 0},
238 { }
239 };
240
241 MODULE_DEVICE_TABLE(acpi, acpi_als_device_ids);
242
243 static struct platform_driver acpi_als_driver = {
244 .probe = acpi_als_probe,
245 .remove = acpi_als_remove,
246 .driver = {
247 .name = "acpi_als",
248 .acpi_match_table = acpi_als_device_ids,
249 },
250 };
251 module_platform_driver(acpi_als_driver);
252
253 MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
254 MODULE_AUTHOR("Martin Liska <marxin.liska@gmail.com>");
255 MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
256 MODULE_DESCRIPTION("ACPI Ambient Light Sensor Driver");
257 MODULE_LICENSE("GPL");
258