270 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Driver for cros-ec proximity sensor exposed through MKBP switch4 *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 26struct 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 35static 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 43static 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 52static 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 59static 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(switches);94 return IIO_VAL_INT;95}96 97static 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(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, timestamp);116 }117 data->last_proximity = state;118 }119 mutex_unlock(&data->lock);120}121 122static 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(switches);138 cros_ec_mkbp_proximity_push_event(data, state);139 }140 141 return NOTIFY_OK;142}143 144static 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, val);153 154 return -EINVAL;155}156 157static 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 167static 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(&data->lock);177 178 return 0;179}180 181static 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 187static 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, &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 204static DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL,205 cros_ec_mkbp_proximity_resume);206 207static 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->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(dev, 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(&ec->event_notifier, &data->notifier);238 239 return 0;240}241 242static 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(&ec->event_notifier,248 &data->notifier);249}250 251static const struct of_device_id cros_ec_mkbp_proximity_of_match[] = {252 { .compatible = "google,cros-ec-mkbp-proximity" },253 {}254};255MODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match);256 257static 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};266module_platform_driver(cros_ec_mkbp_proximity_driver);267 268MODULE_LICENSE("GPL v2");269MODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver");270