brintos

brintos / linux-shallow public Read only

0
0
Text · 7.1 KiB · c25472b Raw
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