1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * cros_ec_light_prox - Driver for light and prox sensors behing CrosEC. |
4 | * |
5 | * Copyright (C) 2017 Google, Inc |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/iio/buffer.h> |
10 | #include <linux/iio/common/cros_ec_sensors_core.h> |
11 | #include <linux/iio/iio.h> |
12 | #include <linux/iio/kfifo_buf.h> |
13 | #include <linux/iio/trigger.h> |
14 | #include <linux/iio/triggered_buffer.h> |
15 | #include <linux/iio/trigger_consumer.h> |
16 | #include <linux/kernel.h> |
17 | #include <linux/mod_devicetable.h> |
18 | #include <linux/module.h> |
19 | #include <linux/platform_data/cros_ec_commands.h> |
20 | #include <linux/platform_data/cros_ec_proto.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/slab.h> |
23 | |
24 | /* |
25 | * We only represent one entry for light or proximity. EC is merging different |
26 | * light sensors to return the what the eye would see. For proximity, we |
27 | * currently support only one light source. |
28 | */ |
29 | #define CROS_EC_LIGHT_PROX_MAX_CHANNELS (1 + 1) |
30 | |
31 | /* State data for ec_sensors iio driver. */ |
32 | struct cros_ec_light_prox_state { |
33 | /* Shared by all sensors */ |
34 | struct cros_ec_sensors_core_state core; |
35 | |
36 | struct iio_chan_spec channels[CROS_EC_LIGHT_PROX_MAX_CHANNELS]; |
37 | }; |
38 | |
39 | static int cros_ec_light_prox_read(struct iio_dev *indio_dev, |
40 | struct iio_chan_spec const *chan, |
41 | int *val, int *val2, long mask) |
42 | { |
43 | struct cros_ec_light_prox_state *st = iio_priv(indio_dev); |
44 | u16 data = 0; |
45 | s64 val64; |
46 | int ret; |
47 | int idx = chan->scan_index; |
48 | |
49 | mutex_lock(&st->core.cmd_lock); |
50 | |
51 | switch (mask) { |
52 | case IIO_CHAN_INFO_RAW: |
53 | if (chan->type == IIO_PROXIMITY) { |
54 | ret = cros_ec_sensors_read_cmd(indio_dev, scan_mask: 1 << idx, |
55 | data: (s16 *)&data); |
56 | if (ret) |
57 | break; |
58 | *val = data; |
59 | ret = IIO_VAL_INT; |
60 | } else { |
61 | ret = -EINVAL; |
62 | } |
63 | break; |
64 | case IIO_CHAN_INFO_PROCESSED: |
65 | if (chan->type == IIO_LIGHT) { |
66 | ret = cros_ec_sensors_read_cmd(indio_dev, scan_mask: 1 << idx, |
67 | data: (s16 *)&data); |
68 | if (ret) |
69 | break; |
70 | /* |
71 | * The data coming from the light sensor is |
72 | * pre-processed and represents the ambient light |
73 | * illuminance reading expressed in lux. |
74 | */ |
75 | *val = data; |
76 | ret = IIO_VAL_INT; |
77 | } else { |
78 | ret = -EINVAL; |
79 | } |
80 | break; |
81 | case IIO_CHAN_INFO_CALIBBIAS: |
82 | st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; |
83 | st->core.param.sensor_offset.flags = 0; |
84 | |
85 | ret = cros_ec_motion_send_host_cmd(st: &st->core, opt_length: 0); |
86 | if (ret) |
87 | break; |
88 | |
89 | /* Save values */ |
90 | st->core.calib[0].offset = |
91 | st->core.resp->sensor_offset.offset[0]; |
92 | |
93 | *val = st->core.calib[idx].offset; |
94 | ret = IIO_VAL_INT; |
95 | break; |
96 | case IIO_CHAN_INFO_CALIBSCALE: |
97 | /* |
98 | * RANGE is used for calibration |
99 | * scale is a number x.y, where x is coded on 16 bits, |
100 | * y coded on 16 bits, between 0 and 9999. |
101 | */ |
102 | st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; |
103 | st->core.param.sensor_range.data = EC_MOTION_SENSE_NO_VALUE; |
104 | |
105 | ret = cros_ec_motion_send_host_cmd(st: &st->core, opt_length: 0); |
106 | if (ret) |
107 | break; |
108 | |
109 | val64 = st->core.resp->sensor_range.ret; |
110 | *val = val64 >> 16; |
111 | *val2 = (val64 & 0xffff) * 100; |
112 | ret = IIO_VAL_INT_PLUS_MICRO; |
113 | break; |
114 | default: |
115 | ret = cros_ec_sensors_core_read(st: &st->core, chan, val, val2, |
116 | mask); |
117 | break; |
118 | } |
119 | |
120 | mutex_unlock(lock: &st->core.cmd_lock); |
121 | |
122 | return ret; |
123 | } |
124 | |
125 | static int cros_ec_light_prox_write(struct iio_dev *indio_dev, |
126 | struct iio_chan_spec const *chan, |
127 | int val, int val2, long mask) |
128 | { |
129 | struct cros_ec_light_prox_state *st = iio_priv(indio_dev); |
130 | int ret; |
131 | int idx = chan->scan_index; |
132 | |
133 | mutex_lock(&st->core.cmd_lock); |
134 | |
135 | switch (mask) { |
136 | case IIO_CHAN_INFO_CALIBBIAS: |
137 | st->core.calib[idx].offset = val; |
138 | /* Send to EC for each axis, even if not complete */ |
139 | st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_OFFSET; |
140 | st->core.param.sensor_offset.flags = MOTION_SENSE_SET_OFFSET; |
141 | st->core.param.sensor_offset.offset[0] = |
142 | st->core.calib[0].offset; |
143 | st->core.param.sensor_offset.temp = |
144 | EC_MOTION_SENSE_INVALID_CALIB_TEMP; |
145 | ret = cros_ec_motion_send_host_cmd(st: &st->core, opt_length: 0); |
146 | break; |
147 | case IIO_CHAN_INFO_CALIBSCALE: |
148 | st->core.param.cmd = MOTIONSENSE_CMD_SENSOR_RANGE; |
149 | st->core.curr_range = (val << 16) | (val2 / 100); |
150 | st->core.param.sensor_range.data = st->core.curr_range; |
151 | ret = cros_ec_motion_send_host_cmd(st: &st->core, opt_length: 0); |
152 | if (ret == 0) |
153 | st->core.range_updated = true; |
154 | break; |
155 | default: |
156 | ret = cros_ec_sensors_core_write(st: &st->core, chan, val, val2, |
157 | mask); |
158 | break; |
159 | } |
160 | |
161 | mutex_unlock(lock: &st->core.cmd_lock); |
162 | |
163 | return ret; |
164 | } |
165 | |
166 | static const struct iio_info cros_ec_light_prox_info = { |
167 | .read_raw = &cros_ec_light_prox_read, |
168 | .write_raw = &cros_ec_light_prox_write, |
169 | .read_avail = &cros_ec_sensors_core_read_avail, |
170 | }; |
171 | |
172 | static int cros_ec_light_prox_probe(struct platform_device *pdev) |
173 | { |
174 | struct device *dev = &pdev->dev; |
175 | struct iio_dev *indio_dev; |
176 | struct cros_ec_light_prox_state *state; |
177 | struct iio_chan_spec *channel; |
178 | int ret; |
179 | |
180 | indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*state)); |
181 | if (!indio_dev) |
182 | return -ENOMEM; |
183 | |
184 | ret = cros_ec_sensors_core_init(pdev, indio_dev, physical_device: true, |
185 | trigger_capture: cros_ec_sensors_capture); |
186 | if (ret) |
187 | return ret; |
188 | |
189 | indio_dev->info = &cros_ec_light_prox_info; |
190 | state = iio_priv(indio_dev); |
191 | channel = state->channels; |
192 | |
193 | /* Common part */ |
194 | channel->info_mask_shared_by_all = |
195 | BIT(IIO_CHAN_INFO_SAMP_FREQ); |
196 | channel->info_mask_shared_by_all_available = |
197 | BIT(IIO_CHAN_INFO_SAMP_FREQ); |
198 | channel->scan_type.realbits = CROS_EC_SENSOR_BITS; |
199 | channel->scan_type.storagebits = CROS_EC_SENSOR_BITS; |
200 | channel->scan_type.shift = 0; |
201 | channel->scan_index = 0; |
202 | channel->ext_info = cros_ec_sensors_ext_info; |
203 | channel->scan_type.sign = 'u'; |
204 | |
205 | /* Sensor specific */ |
206 | switch (state->core.type) { |
207 | case MOTIONSENSE_TYPE_LIGHT: |
208 | channel->type = IIO_LIGHT; |
209 | channel->info_mask_separate = |
210 | BIT(IIO_CHAN_INFO_PROCESSED) | |
211 | BIT(IIO_CHAN_INFO_CALIBBIAS) | |
212 | BIT(IIO_CHAN_INFO_CALIBSCALE); |
213 | break; |
214 | case MOTIONSENSE_TYPE_PROX: |
215 | channel->type = IIO_PROXIMITY; |
216 | channel->info_mask_separate = |
217 | BIT(IIO_CHAN_INFO_RAW) | |
218 | BIT(IIO_CHAN_INFO_CALIBBIAS) | |
219 | BIT(IIO_CHAN_INFO_CALIBSCALE); |
220 | break; |
221 | default: |
222 | dev_warn(dev, "Unknown motion sensor\n" ); |
223 | return -EINVAL; |
224 | } |
225 | |
226 | /* Timestamp */ |
227 | channel++; |
228 | channel->type = IIO_TIMESTAMP; |
229 | channel->channel = -1; |
230 | channel->scan_index = 1; |
231 | channel->scan_type.sign = 's'; |
232 | channel->scan_type.realbits = 64; |
233 | channel->scan_type.storagebits = 64; |
234 | |
235 | indio_dev->channels = state->channels; |
236 | |
237 | indio_dev->num_channels = CROS_EC_LIGHT_PROX_MAX_CHANNELS; |
238 | |
239 | state->core.read_ec_sensors_data = cros_ec_sensors_read_cmd; |
240 | |
241 | return cros_ec_sensors_core_register(dev, indio_dev, |
242 | push_data: cros_ec_sensors_push_data); |
243 | } |
244 | |
245 | static const struct platform_device_id cros_ec_light_prox_ids[] = { |
246 | { |
247 | .name = "cros-ec-prox" , |
248 | }, |
249 | { |
250 | .name = "cros-ec-light" , |
251 | }, |
252 | { /* sentinel */ } |
253 | }; |
254 | MODULE_DEVICE_TABLE(platform, cros_ec_light_prox_ids); |
255 | |
256 | static struct platform_driver cros_ec_light_prox_platform_driver = { |
257 | .driver = { |
258 | .name = "cros-ec-light-prox" , |
259 | .pm = &cros_ec_sensors_pm_ops, |
260 | }, |
261 | .probe = cros_ec_light_prox_probe, |
262 | .id_table = cros_ec_light_prox_ids, |
263 | }; |
264 | module_platform_driver(cros_ec_light_prox_platform_driver); |
265 | |
266 | MODULE_DESCRIPTION("ChromeOS EC light/proximity sensors driver" ); |
267 | MODULE_LICENSE("GPL v2" ); |
268 | |