xref: /linux/drivers/iio/light/acpi-als.c (revision cb4eb6771c0f8fd1c52a8f6fdec7762fb087380a)
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