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/mod_devicetable.h> 10 #include <linux/module.h> 11 #include <linux/mutex.h> 12 #include <linux/notifier.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 <linux/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(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 DEFINE_RAW_FLEX(struct cros_ec_command, buf, data, 63 MAX(sizeof(u32), sizeof(struct ec_params_mkbp_info))); 64 struct ec_params_mkbp_info *params = (struct ec_params_mkbp_info *)buf->data; 65 struct cros_ec_command *msg = buf; 66 u32 *switches = (u32 *)buf->data; 67 size_t insize = sizeof(*switches); 68 int ret; 69 70 msg->command = EC_CMD_MKBP_INFO; 71 msg->version = 1; 72 msg->outsize = sizeof(*params); 73 msg->insize = insize; 74 75 params->info_type = EC_MKBP_INFO_CURRENT; 76 params->event_type = EC_MKBP_EVENT_SWITCH; 77 78 ret = cros_ec_cmd_xfer_status(ec_dev, msg); 79 if (ret < 0) 80 return ret; 81 82 if (ret != insize) { 83 dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", ret, 84 insize); 85 return -EPROTO; 86 } 87 88 *state = cros_ec_mkbp_proximity_parse_state(switches); 89 return IIO_VAL_INT; 90 } 91 92 static void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state) 93 { 94 s64 timestamp; 95 u64 ev; 96 int dir; 97 struct iio_dev *indio_dev = data->indio_dev; 98 struct cros_ec_device *ec = data->ec; 99 100 mutex_lock(&data->lock); 101 if (state != data->last_proximity) { 102 if (data->enabled) { 103 timestamp = ktime_to_ns(ec->last_event_time); 104 if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME) 105 timestamp = iio_get_time_ns(indio_dev); 106 107 dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING; 108 ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0, 109 IIO_EV_TYPE_THRESH, dir); 110 iio_push_event(indio_dev, ev, timestamp); 111 } 112 data->last_proximity = state; 113 } 114 mutex_unlock(&data->lock); 115 } 116 117 static int cros_ec_mkbp_proximity_notify(struct notifier_block *nb, 118 unsigned long queued_during_suspend, 119 void *_ec) 120 { 121 struct cros_ec_mkbp_proximity_data *data; 122 struct cros_ec_device *ec = _ec; 123 u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK; 124 void *switches; 125 int state; 126 127 if (event_type == EC_MKBP_EVENT_SWITCH) { 128 data = container_of(nb, struct cros_ec_mkbp_proximity_data, 129 notifier); 130 131 switches = &ec->event_data.data.switches; 132 state = cros_ec_mkbp_proximity_parse_state(switches); 133 cros_ec_mkbp_proximity_push_event(data, state); 134 } 135 136 return NOTIFY_OK; 137 } 138 139 static int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev, 140 const struct iio_chan_spec *chan, int *val, 141 int *val2, long mask) 142 { 143 struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); 144 struct cros_ec_device *ec = data->ec; 145 146 if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW) 147 return cros_ec_mkbp_proximity_query(ec, val); 148 149 return -EINVAL; 150 } 151 152 static int cros_ec_mkbp_proximity_read_event_config(struct iio_dev *indio_dev, 153 const struct iio_chan_spec *chan, 154 enum iio_event_type type, 155 enum iio_event_direction dir) 156 { 157 struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); 158 159 return data->enabled; 160 } 161 162 static int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev, 163 const struct iio_chan_spec *chan, 164 enum iio_event_type type, 165 enum iio_event_direction dir, bool state) 166 { 167 struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev); 168 169 mutex_lock(&data->lock); 170 data->enabled = state; 171 mutex_unlock(&data->lock); 172 173 return 0; 174 } 175 176 static const struct iio_info cros_ec_mkbp_proximity_info = { 177 .read_raw = cros_ec_mkbp_proximity_read_raw, 178 .read_event_config = cros_ec_mkbp_proximity_read_event_config, 179 .write_event_config = cros_ec_mkbp_proximity_write_event_config, 180 }; 181 182 static int cros_ec_mkbp_proximity_resume(struct device *dev) 183 { 184 struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev); 185 struct cros_ec_device *ec = data->ec; 186 int ret, state; 187 188 ret = cros_ec_mkbp_proximity_query(ec, &state); 189 if (ret < 0) { 190 dev_warn(dev, "failed to fetch proximity state on resume: %d\n", 191 ret); 192 } else { 193 cros_ec_mkbp_proximity_push_event(data, state); 194 } 195 196 return 0; 197 } 198 199 static DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL, 200 cros_ec_mkbp_proximity_resume); 201 202 static int cros_ec_mkbp_proximity_probe(struct platform_device *pdev) 203 { 204 struct device *dev = &pdev->dev; 205 struct cros_ec_device *ec = dev_get_drvdata(dev->parent); 206 struct iio_dev *indio_dev; 207 struct cros_ec_mkbp_proximity_data *data; 208 int ret; 209 210 indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); 211 if (!indio_dev) 212 return -ENOMEM; 213 214 data = iio_priv(indio_dev); 215 data->ec = ec; 216 data->indio_dev = indio_dev; 217 data->last_proximity = -1; /* Unknown to start */ 218 mutex_init(&data->lock); 219 platform_set_drvdata(pdev, data); 220 221 indio_dev->name = dev->driver->name; 222 indio_dev->info = &cros_ec_mkbp_proximity_info; 223 indio_dev->modes = INDIO_DIRECT_MODE; 224 indio_dev->channels = cros_ec_mkbp_proximity_chan_spec; 225 indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec); 226 227 ret = devm_iio_device_register(dev, indio_dev); 228 if (ret) 229 return ret; 230 231 data->notifier.notifier_call = cros_ec_mkbp_proximity_notify; 232 blocking_notifier_chain_register(&ec->event_notifier, &data->notifier); 233 234 return 0; 235 } 236 237 static void cros_ec_mkbp_proximity_remove(struct platform_device *pdev) 238 { 239 struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev); 240 struct cros_ec_device *ec = data->ec; 241 242 blocking_notifier_chain_unregister(&ec->event_notifier, 243 &data->notifier); 244 } 245 246 static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = { 247 { .compatible = "google,cros-ec-mkbp-proximity" }, 248 { } 249 }; 250 MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match); 251 252 static struct platform_driver cros_ec_mkbp_proximity_driver = { 253 .driver = { 254 .name = "cros-ec-mkbp-proximity", 255 .of_match_table = cros_ec_mkbp_proximity_of_match, 256 .pm = pm_sleep_ptr(&cros_ec_mkbp_proximity_pm_ops), 257 }, 258 .probe = cros_ec_mkbp_proximity_probe, 259 .remove = cros_ec_mkbp_proximity_remove, 260 }; 261 module_platform_driver(cros_ec_mkbp_proximity_driver); 262 263 MODULE_LICENSE("GPL v2"); 264 MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver"); 265