Commit | Line | Data |
---|---|---|
37aa055c | 1 | // SPDX-License-Identifier: GPL-2.0 |
11b86c70 GG |
2 | /* |
3 | * Driver for older Chrome OS EC accelerometer | |
4 | * | |
5 | * Copyright 2017 Google, Inc | |
6 | * | |
11b86c70 GG |
7 | * This driver uses the memory mapper cros-ec interface to communicate |
8 | * with the Chrome OS EC about accelerometer data. | |
9 | * Accelerometer access is presented through iio sysfs. | |
10 | */ | |
11 | ||
12 | #include <linux/delay.h> | |
13 | #include <linux/device.h> | |
14 | #include <linux/iio/buffer.h> | |
15 | #include <linux/iio/iio.h> | |
16 | #include <linux/iio/kfifo_buf.h> | |
17 | #include <linux/iio/trigger_consumer.h> | |
18 | #include <linux/iio/triggered_buffer.h> | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/mfd/cros_ec.h> | |
21 | #include <linux/mfd/cros_ec_commands.h> | |
22 | #include <linux/module.h> | |
23 | #include <linux/slab.h> | |
11b86c70 GG |
24 | #include <linux/platform_device.h> |
25 | ||
26 | #define DRV_NAME "cros-ec-accel-legacy" | |
27 | ||
28 | /* | |
29 | * Sensor scale hard coded at 10 bits per g, computed as: | |
30 | * g / (2^10 - 1) = 0.009586168; with g = 9.80665 m.s^-2 | |
31 | */ | |
32 | #define ACCEL_LEGACY_NSCALE 9586168 | |
33 | ||
34 | /* Indices for EC sensor values. */ | |
35 | enum { | |
36 | X, | |
37 | Y, | |
38 | Z, | |
39 | MAX_AXIS, | |
40 | }; | |
41 | ||
42 | /* State data for cros_ec_accel_legacy iio driver. */ | |
43 | struct cros_ec_accel_legacy_state { | |
44 | struct cros_ec_device *ec; | |
45 | ||
46 | /* | |
47 | * Array holding data from a single capture. 2 bytes per channel | |
48 | * for the 3 channels plus the timestamp which is always last and | |
49 | * 8-bytes aligned. | |
50 | */ | |
51 | s16 capture_data[8]; | |
52 | s8 sign[MAX_AXIS]; | |
53 | u8 sensor_num; | |
54 | }; | |
55 | ||
56 | static int ec_cmd_read_u8(struct cros_ec_device *ec, unsigned int offset, | |
57 | u8 *dest) | |
58 | { | |
59 | return ec->cmd_readmem(ec, offset, 1, dest); | |
60 | } | |
61 | ||
62 | static int ec_cmd_read_u16(struct cros_ec_device *ec, unsigned int offset, | |
63 | u16 *dest) | |
64 | { | |
65 | __le16 tmp; | |
66 | int ret = ec->cmd_readmem(ec, offset, 2, &tmp); | |
67 | ||
68 | *dest = le16_to_cpu(tmp); | |
69 | ||
70 | return ret; | |
71 | } | |
72 | ||
73 | /** | |
74 | * read_ec_until_not_busy() - Read from EC status byte until it reads not busy. | |
75 | * @st: Pointer to state information for device. | |
76 | * | |
77 | * This function reads EC status until its busy bit gets cleared. It does not | |
78 | * wait indefinitely and returns -EIO if the EC status is still busy after a | |
79 | * few hundreds milliseconds. | |
80 | * | |
81 | * Return: 8-bit status if ok, -EIO on error | |
82 | */ | |
83 | static int read_ec_until_not_busy(struct cros_ec_accel_legacy_state *st) | |
84 | { | |
85 | struct cros_ec_device *ec = st->ec; | |
86 | u8 status; | |
87 | int attempts = 0; | |
88 | ||
89 | ec_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); | |
90 | while (status & EC_MEMMAP_ACC_STATUS_BUSY_BIT) { | |
91 | /* Give up after enough attempts, return error. */ | |
92 | if (attempts++ >= 50) | |
93 | return -EIO; | |
94 | ||
95 | /* Small delay every so often. */ | |
96 | if (attempts % 5 == 0) | |
97 | msleep(25); | |
98 | ||
99 | ec_cmd_read_u8(ec, EC_MEMMAP_ACC_STATUS, &status); | |
100 | } | |
101 | ||
102 | return status; | |
103 | } | |
104 | ||
105 | /** | |
106 | * read_ec_accel_data_unsafe() - Read acceleration data from EC shared memory. | |
107 | * @st: Pointer to state information for device. | |
108 | * @scan_mask: Bitmap of the sensor indices to scan. | |
109 | * @data: Location to store data. | |
110 | * | |
111 | * This is the unsafe function for reading the EC data. It does not guarantee | |
112 | * that the EC will not modify the data as it is being read in. | |
113 | */ | |
114 | static void read_ec_accel_data_unsafe(struct cros_ec_accel_legacy_state *st, | |
115 | unsigned long scan_mask, s16 *data) | |
116 | { | |
117 | int i = 0; | |
118 | int num_enabled = bitmap_weight(&scan_mask, MAX_AXIS); | |
119 | ||
120 | /* Read all sensors enabled in scan_mask. Each value is 2 bytes. */ | |
121 | while (num_enabled--) { | |
122 | i = find_next_bit(&scan_mask, MAX_AXIS, i); | |
123 | ec_cmd_read_u16(st->ec, | |
124 | EC_MEMMAP_ACC_DATA + | |
125 | sizeof(s16) * | |
126 | (1 + i + st->sensor_num * MAX_AXIS), | |
127 | data); | |
128 | *data *= st->sign[i]; | |
129 | i++; | |
130 | data++; | |
131 | } | |
132 | } | |
133 | ||
134 | /** | |
135 | * read_ec_accel_data() - Read acceleration data from EC shared memory. | |
136 | * @st: Pointer to state information for device. | |
137 | * @scan_mask: Bitmap of the sensor indices to scan. | |
138 | * @data: Location to store data. | |
139 | * | |
140 | * This is the safe function for reading the EC data. It guarantees that | |
141 | * the data sampled was not modified by the EC while being read. | |
142 | * | |
143 | * Return: 0 if ok, -ve on error | |
144 | */ | |
145 | static int read_ec_accel_data(struct cros_ec_accel_legacy_state *st, | |
146 | unsigned long scan_mask, s16 *data) | |
147 | { | |
148 | u8 samp_id = 0xff; | |
149 | u8 status = 0; | |
150 | int ret; | |
151 | int attempts = 0; | |
152 | ||
153 | /* | |
154 | * Continually read all data from EC until the status byte after | |
155 | * all reads reflects that the EC is not busy and the sample id | |
156 | * matches the sample id from before all reads. This guarantees | |
157 | * that data read in was not modified by the EC while reading. | |
158 | */ | |
159 | while ((status & (EC_MEMMAP_ACC_STATUS_BUSY_BIT | | |
160 | EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK)) != samp_id) { | |
161 | /* If we have tried to read too many times, return error. */ | |
162 | if (attempts++ >= 5) | |
163 | return -EIO; | |
164 | ||
165 | /* Read status byte until EC is not busy. */ | |
166 | ret = read_ec_until_not_busy(st); | |
167 | if (ret < 0) | |
168 | return ret; | |
169 | status = ret; | |
170 | ||
171 | /* | |
172 | * Store the current sample id so that we can compare to the | |
173 | * sample id after reading the data. | |
174 | */ | |
175 | samp_id = status & EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK; | |
176 | ||
177 | /* Read all EC data, format it, and store it into data. */ | |
178 | read_ec_accel_data_unsafe(st, scan_mask, data); | |
179 | ||
180 | /* Read status byte. */ | |
181 | ec_cmd_read_u8(st->ec, EC_MEMMAP_ACC_STATUS, &status); | |
182 | } | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static int cros_ec_accel_legacy_read(struct iio_dev *indio_dev, | |
188 | struct iio_chan_spec const *chan, | |
189 | int *val, int *val2, long mask) | |
190 | { | |
191 | struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); | |
192 | s16 data = 0; | |
193 | int ret = IIO_VAL_INT; | |
194 | ||
195 | switch (mask) { | |
196 | case IIO_CHAN_INFO_RAW: | |
197 | ret = read_ec_accel_data(st, (1 << chan->scan_index), &data); | |
198 | if (ret) | |
199 | return ret; | |
200 | *val = data; | |
201 | return IIO_VAL_INT; | |
202 | case IIO_CHAN_INFO_SCALE: | |
203 | *val = 0; | |
204 | *val2 = ACCEL_LEGACY_NSCALE; | |
205 | return IIO_VAL_INT_PLUS_NANO; | |
206 | case IIO_CHAN_INFO_CALIBBIAS: | |
207 | /* Calibration not supported. */ | |
208 | *val = 0; | |
209 | return IIO_VAL_INT; | |
210 | default: | |
211 | return -EINVAL; | |
212 | } | |
213 | } | |
214 | ||
215 | static int cros_ec_accel_legacy_write(struct iio_dev *indio_dev, | |
216 | struct iio_chan_spec const *chan, | |
217 | int val, int val2, long mask) | |
218 | { | |
219 | /* | |
220 | * Do nothing but don't return an error code to allow calibration | |
221 | * script to work. | |
222 | */ | |
223 | if (mask == IIO_CHAN_INFO_CALIBBIAS) | |
224 | return 0; | |
225 | ||
226 | return -EINVAL; | |
227 | } | |
228 | ||
229 | static const struct iio_info cros_ec_accel_legacy_info = { | |
230 | .read_raw = &cros_ec_accel_legacy_read, | |
231 | .write_raw = &cros_ec_accel_legacy_write, | |
232 | }; | |
233 | ||
234 | /** | |
235 | * cros_ec_accel_legacy_capture() - The trigger handler function | |
236 | * @irq: The interrupt number. | |
237 | * @p: Private data - always a pointer to the poll func. | |
238 | * | |
239 | * On a trigger event occurring, if the pollfunc is attached then this | |
240 | * handler is called as a threaded interrupt (and hence may sleep). It | |
241 | * is responsible for grabbing data from the device and pushing it into | |
242 | * the associated buffer. | |
243 | * | |
244 | * Return: IRQ_HANDLED | |
245 | */ | |
246 | static irqreturn_t cros_ec_accel_legacy_capture(int irq, void *p) | |
247 | { | |
248 | struct iio_poll_func *pf = p; | |
249 | struct iio_dev *indio_dev = pf->indio_dev; | |
250 | struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); | |
251 | ||
252 | /* Clear capture data. */ | |
253 | memset(st->capture_data, 0, sizeof(st->capture_data)); | |
254 | ||
255 | /* | |
256 | * Read data based on which channels are enabled in scan mask. Note | |
257 | * that on a capture we are always reading the calibrated data. | |
258 | */ | |
259 | read_ec_accel_data(st, *indio_dev->active_scan_mask, st->capture_data); | |
260 | ||
261 | iio_push_to_buffers_with_timestamp(indio_dev, (void *)st->capture_data, | |
262 | iio_get_time_ns(indio_dev)); | |
263 | ||
264 | /* | |
265 | * Tell the core we are done with this trigger and ready for the | |
266 | * next one. | |
267 | */ | |
268 | iio_trigger_notify_done(indio_dev->trig); | |
269 | ||
270 | return IRQ_HANDLED; | |
271 | } | |
272 | ||
273 | static char *cros_ec_accel_legacy_loc_strings[] = { | |
274 | [MOTIONSENSE_LOC_BASE] = "base", | |
275 | [MOTIONSENSE_LOC_LID] = "lid", | |
276 | [MOTIONSENSE_LOC_MAX] = "unknown", | |
277 | }; | |
278 | ||
279 | static ssize_t cros_ec_accel_legacy_loc(struct iio_dev *indio_dev, | |
280 | uintptr_t private, | |
281 | const struct iio_chan_spec *chan, | |
282 | char *buf) | |
283 | { | |
284 | struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); | |
285 | ||
286 | return sprintf(buf, "%s\n", | |
287 | cros_ec_accel_legacy_loc_strings[st->sensor_num + | |
288 | MOTIONSENSE_LOC_BASE]); | |
289 | } | |
290 | ||
291 | static ssize_t cros_ec_accel_legacy_id(struct iio_dev *indio_dev, | |
292 | uintptr_t private, | |
293 | const struct iio_chan_spec *chan, | |
294 | char *buf) | |
295 | { | |
296 | struct cros_ec_accel_legacy_state *st = iio_priv(indio_dev); | |
297 | ||
298 | return sprintf(buf, "%d\n", st->sensor_num); | |
299 | } | |
300 | ||
301 | static const struct iio_chan_spec_ext_info cros_ec_accel_legacy_ext_info[] = { | |
302 | { | |
303 | .name = "id", | |
304 | .shared = IIO_SHARED_BY_ALL, | |
305 | .read = cros_ec_accel_legacy_id, | |
306 | }, | |
307 | { | |
308 | .name = "location", | |
309 | .shared = IIO_SHARED_BY_ALL, | |
310 | .read = cros_ec_accel_legacy_loc, | |
311 | }, | |
312 | { } | |
313 | }; | |
314 | ||
315 | #define CROS_EC_ACCEL_LEGACY_CHAN(_axis) \ | |
316 | { \ | |
317 | .type = IIO_ACCEL, \ | |
318 | .channel2 = IIO_MOD_X + (_axis), \ | |
319 | .modified = 1, \ | |
320 | .info_mask_separate = \ | |
321 | BIT(IIO_CHAN_INFO_RAW) | \ | |
11b86c70 GG |
322 | BIT(IIO_CHAN_INFO_CALIBBIAS), \ |
323 | .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \ | |
324 | .ext_info = cros_ec_accel_legacy_ext_info, \ | |
325 | .scan_type = { \ | |
326 | .sign = 's', \ | |
327 | .realbits = 16, \ | |
328 | .storagebits = 16, \ | |
329 | }, \ | |
330 | } \ | |
331 | ||
332 | static struct iio_chan_spec ec_accel_channels[] = { | |
333 | CROS_EC_ACCEL_LEGACY_CHAN(X), | |
334 | CROS_EC_ACCEL_LEGACY_CHAN(Y), | |
335 | CROS_EC_ACCEL_LEGACY_CHAN(Z), | |
336 | IIO_CHAN_SOFT_TIMESTAMP(MAX_AXIS) | |
337 | }; | |
338 | ||
339 | static int cros_ec_accel_legacy_probe(struct platform_device *pdev) | |
340 | { | |
341 | struct device *dev = &pdev->dev; | |
342 | struct cros_ec_dev *ec = dev_get_drvdata(dev->parent); | |
343 | struct cros_ec_sensor_platform *sensor_platform = dev_get_platdata(dev); | |
344 | struct iio_dev *indio_dev; | |
345 | struct cros_ec_accel_legacy_state *state; | |
74c420e0 | 346 | int ret; |
11b86c70 GG |
347 | |
348 | if (!ec || !ec->ec_dev) { | |
349 | dev_warn(&pdev->dev, "No EC device found.\n"); | |
350 | return -EINVAL; | |
351 | } | |
352 | ||
353 | if (!ec->ec_dev->cmd_readmem) { | |
354 | dev_warn(&pdev->dev, "EC does not support direct reads.\n"); | |
355 | return -EINVAL; | |
356 | } | |
357 | ||
358 | indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*state)); | |
359 | if (!indio_dev) | |
360 | return -ENOMEM; | |
361 | ||
362 | platform_set_drvdata(pdev, indio_dev); | |
363 | state = iio_priv(indio_dev); | |
364 | state->ec = ec->ec_dev; | |
365 | state->sensor_num = sensor_platform->sensor_num; | |
366 | ||
367 | indio_dev->dev.parent = dev; | |
368 | indio_dev->name = pdev->name; | |
369 | indio_dev->channels = ec_accel_channels; | |
370 | /* | |
371 | * Present the channel using HTML5 standard: | |
372 | * need to invert X and Y and invert some lid axis. | |
373 | */ | |
74c420e0 GS |
374 | ec_accel_channels[X].scan_index = Y; |
375 | ec_accel_channels[Y].scan_index = X; | |
376 | ec_accel_channels[Z].scan_index = Z; | |
377 | ||
378 | state->sign[Y] = 1; | |
379 | ||
380 | if (state->sensor_num == MOTIONSENSE_LOC_LID) | |
381 | state->sign[X] = state->sign[Z] = -1; | |
382 | else | |
383 | state->sign[X] = state->sign[Z] = 1; | |
384 | ||
11b86c70 GG |
385 | indio_dev->num_channels = ARRAY_SIZE(ec_accel_channels); |
386 | indio_dev->dev.parent = &pdev->dev; | |
387 | indio_dev->info = &cros_ec_accel_legacy_info; | |
388 | indio_dev->modes = INDIO_DIRECT_MODE; | |
389 | ||
390 | ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, | |
391 | cros_ec_accel_legacy_capture, | |
392 | NULL); | |
393 | if (ret) | |
394 | return ret; | |
395 | ||
396 | return devm_iio_device_register(dev, indio_dev); | |
397 | } | |
398 | ||
399 | static struct platform_driver cros_ec_accel_platform_driver = { | |
400 | .driver = { | |
401 | .name = DRV_NAME, | |
402 | }, | |
403 | .probe = cros_ec_accel_legacy_probe, | |
404 | }; | |
405 | module_platform_driver(cros_ec_accel_platform_driver); | |
406 | ||
407 | MODULE_DESCRIPTION("ChromeOS EC legacy accelerometer driver"); | |
408 | MODULE_AUTHOR("Gwendal Grignou <gwendal@chromium.org>"); | |
37aa055c | 409 | MODULE_LICENSE("GPL v2"); |
11b86c70 | 410 | MODULE_ALIAS("platform:" DRV_NAME); |