1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Driver for cros-ec proximity sensor exposed through MKBP switch |
4 | * |
5 | * Copyright 2021 Google LLC. |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/module.h> |
10 | #include <linux/mutex.h> |
11 | #include <linux/notifier.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/types.h> |
16 | |
17 | #include <linux/platform_data/cros_ec_commands.h> |
18 | #include <linux/platform_data/cros_ec_proto.h> |
19 | |
20 | #include <linux/iio/events.h> |
21 | #include <linux/iio/iio.h> |
22 | #include <linux/iio/sysfs.h> |
23 | |
24 | #include <asm/unaligned.h> |
25 | |
26 | struct cros_ec_mkbp_proximity_data { |
27 | struct cros_ec_device *ec; |
28 | struct iio_dev *indio_dev; |
29 | struct mutex lock; |
30 | struct notifier_block notifier; |
31 | int last_proximity; |
32 | bool enabled; |
33 | }; |
34 | |
35 | static const struct iio_event_spec cros_ec_mkbp_proximity_events[] = { |
36 | { |
37 | .type = IIO_EV_TYPE_THRESH, |
38 | .dir = IIO_EV_DIR_EITHER, |
39 | .mask_separate = BIT(IIO_EV_INFO_ENABLE), |
40 | }, |
41 | }; |
42 | |
43 | static const struct iio_chan_spec cros_ec_mkbp_proximity_chan_spec[] = { |
44 | { |
45 | .type = IIO_PROXIMITY, |
46 | .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), |
47 | .event_spec = cros_ec_mkbp_proximity_events, |
48 | .num_event_specs = ARRAY_SIZE(cros_ec_mkbp_proximity_events), |
49 | }, |
50 | }; |
51 | |
52 | static int cros_ec_mkbp_proximity_parse_state(const void *data) |
53 | { |
54 | u32 switches = get_unaligned_le32(p: data); |
55 | |
56 | return !!(switches & BIT(EC_MKBP_FRONT_PROXIMITY)); |
57 | } |
58 | |
59 | static int cros_ec_mkbp_proximity_query(struct cros_ec_device *ec_dev, |
60 | int *state) |
61 | { |
62 | struct { |
63 | struct cros_ec_command msg; |
64 | union { |
65 | struct ec_params_mkbp_info params; |
66 | u32 switches; |
67 | }; |
68 | } __packed buf = { }; |
69 | struct ec_params_mkbp_info *params = &buf.params; |
70 | struct cros_ec_command *msg = &buf.msg; |
71 | u32 *switches = &buf.switches; |
72 | size_t insize = sizeof(*switches); |
73 | int ret; |
74 | |
75 | msg->command = EC_CMD_MKBP_INFO; |
76 | msg->version = 1; |
77 | msg->outsize = sizeof(*params); |
78 | msg->insize = insize; |
79 | |
80 | params->info_type = EC_MKBP_INFO_CURRENT; |
81 | params->event_type = EC_MKBP_EVENT_SWITCH; |
82 | |
83 | ret = cros_ec_cmd_xfer_status(ec_dev, msg); |
84 | if (ret < 0) |
85 | return ret; |
86 | |
87 | if (ret != insize) { |
88 | dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n" , ret, |
89 | insize); |
90 | return -EPROTO; |
91 | } |
92 | |
93 | *state = cros_ec_mkbp_proximity_parse_state(data: switches); |
94 | return IIO_VAL_INT; |
95 | } |
96 | |
97 | static void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state) |
98 | { |
99 | s64 timestamp; |
100 | u64 ev; |
101 | int dir; |
102 | struct iio_dev *indio_dev = data->indio_dev; |
103 | struct cros_ec_device *ec = data->ec; |
104 | |
105 | mutex_lock(&data->lock); |
106 | if (state != data->last_proximity) { |
107 | if (data->enabled) { |
108 | timestamp = ktime_to_ns(kt: ec->last_event_time); |
109 | if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) |
110 | timestamp = iio_get_time_ns(indio_dev); |
111 | |
112 | dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; |
113 | ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, |
114 | IIO_EV_TYPE_THRESH, dir); |
115 | iio_push_event(indio_dev, ev_code: ev, timestamp); |
116 | } |
117 | data->last_proximity = state; |
118 | } |
119 | mutex_unlock(lock: &data->lock); |
120 | } |
121 | |
122 | static int cros_ec_mkbp_proximity_notify(struct notifier_block *nb, |
123 | unsigned long queued_during_suspend, |
124 | void *_ec) |
125 | { |
126 | struct cros_ec_mkbp_proximity_data *data; |
127 | struct cros_ec_device *ec = _ec; |
128 | u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK; |
129 | void *switches; |
130 | int state; |
131 | |
132 | if (event_type == EC_MKBP_EVENT_SWITCH) { |
133 | data = container_of(nb, struct cros_ec_mkbp_proximity_data, |
134 | notifier); |
135 | |
136 | switches = &ec->event_data.data.switches; |
137 | state = cros_ec_mkbp_proximity_parse_state(data: switches); |
138 | cros_ec_mkbp_proximity_push_event(data, state); |
139 | } |
140 | |
141 | return NOTIFY_OK; |
142 | } |
143 | |
144 | static int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev, |
145 | const struct iio_chan_spec *chan, int *val, |
146 | int *val2, long mask) |
147 | { |
148 | struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); |
149 | struct cros_ec_device *ec = data->ec; |
150 | |
151 | if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW) |
152 | return cros_ec_mkbp_proximity_query(ec_dev: ec, state: val); |
153 | |
154 | return -EINVAL; |
155 | } |
156 | |
157 | static int cros_ec_mkbp_proximity_read_event_config(struct iio_dev *indio_dev, |
158 | const struct iio_chan_spec *chan, |
159 | enum iio_event_type type, |
160 | enum iio_event_direction dir) |
161 | { |
162 | struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); |
163 | |
164 | return data->enabled; |
165 | } |
166 | |
167 | static int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev, |
168 | const struct iio_chan_spec *chan, |
169 | enum iio_event_type type, |
170 | enum iio_event_direction dir, int state) |
171 | { |
172 | struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); |
173 | |
174 | mutex_lock(&data->lock); |
175 | data->enabled = state; |
176 | mutex_unlock(lock: &data->lock); |
177 | |
178 | return 0; |
179 | } |
180 | |
181 | static const struct iio_info cros_ec_mkbp_proximity_info = { |
182 | .read_raw = cros_ec_mkbp_proximity_read_raw, |
183 | .read_event_config = cros_ec_mkbp_proximity_read_event_config, |
184 | .write_event_config = cros_ec_mkbp_proximity_write_event_config, |
185 | }; |
186 | |
187 | static int cros_ec_mkbp_proximity_resume(struct device *dev) |
188 | { |
189 | struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev); |
190 | struct cros_ec_device *ec = data->ec; |
191 | int ret, state; |
192 | |
193 | ret = cros_ec_mkbp_proximity_query(ec_dev: ec, state: &state); |
194 | if (ret < 0) { |
195 | dev_warn(dev, "failed to fetch proximity state on resume: %d\n" , |
196 | ret); |
197 | } else { |
198 | cros_ec_mkbp_proximity_push_event(data, state); |
199 | } |
200 | |
201 | return 0; |
202 | } |
203 | |
204 | static DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL, |
205 | cros_ec_mkbp_proximity_resume); |
206 | |
207 | static int cros_ec_mkbp_proximity_probe(struct platform_device *pdev) |
208 | { |
209 | struct device *dev = &pdev->dev; |
210 | struct cros_ec_device *ec = dev_get_drvdata(dev: dev->parent); |
211 | struct iio_dev *indio_dev; |
212 | struct cros_ec_mkbp_proximity_data *data; |
213 | int ret; |
214 | |
215 | indio_dev = devm_iio_device_alloc(parent: dev, sizeof_priv: sizeof(*data)); |
216 | if (!indio_dev) |
217 | return -ENOMEM; |
218 | |
219 | data = iio_priv(indio_dev); |
220 | data->ec = ec; |
221 | data->indio_dev = indio_dev; |
222 | data->last_proximity = -1; /* Unknown to start */ |
223 | mutex_init(&data->lock); |
224 | platform_set_drvdata(pdev, data); |
225 | |
226 | indio_dev->name = dev->driver->name; |
227 | indio_dev->info = &cros_ec_mkbp_proximity_info; |
228 | indio_dev->modes = INDIO_DIRECT_MODE; |
229 | indio_dev->channels = cros_ec_mkbp_proximity_chan_spec; |
230 | indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec); |
231 | |
232 | ret = devm_iio_device_register(dev, indio_dev); |
233 | if (ret) |
234 | return ret; |
235 | |
236 | data->notifier.notifier_call = cros_ec_mkbp_proximity_notify; |
237 | blocking_notifier_chain_register(nh: &ec->event_notifier, nb: &data->notifier); |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static void cros_ec_mkbp_proximity_remove(struct platform_device *pdev) |
243 | { |
244 | struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev); |
245 | struct cros_ec_device *ec = data->ec; |
246 | |
247 | blocking_notifier_chain_unregister(nh: &ec->event_notifier, |
248 | nb: &data->notifier); |
249 | } |
250 | |
251 | static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = { |
252 | { .compatible = "google,cros-ec-mkbp-proximity" }, |
253 | {} |
254 | }; |
255 | MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match); |
256 | |
257 | static struct platform_driver cros_ec_mkbp_proximity_driver = { |
258 | .driver = { |
259 | .name = "cros-ec-mkbp-proximity" , |
260 | .of_match_table = cros_ec_mkbp_proximity_of_match, |
261 | .pm = pm_sleep_ptr(&cros_ec_mkbp_proximity_pm_ops), |
262 | }, |
263 | .probe = cros_ec_mkbp_proximity_probe, |
264 | .remove_new = cros_ec_mkbp_proximity_remove, |
265 | }; |
266 | module_platform_driver(cros_ec_mkbp_proximity_driver); |
267 | |
268 | MODULE_LICENSE("GPL v2" ); |
269 | MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver" ); |
270 | |