162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Driver for cros-ec proximity sensor exposed through MKBP switch
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2021 Google LLC.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/kernel.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/mutex.h>
1162306a36Sopenharmony_ci#include <linux/notifier.h>
1262306a36Sopenharmony_ci#include <linux/of.h>
1362306a36Sopenharmony_ci#include <linux/platform_device.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <linux/types.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/platform_data/cros_ec_commands.h>
1862306a36Sopenharmony_ci#include <linux/platform_data/cros_ec_proto.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/iio/events.h>
2162306a36Sopenharmony_ci#include <linux/iio/iio.h>
2262306a36Sopenharmony_ci#include <linux/iio/sysfs.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <asm/unaligned.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct cros_ec_mkbp_proximity_data {
2762306a36Sopenharmony_ci	struct cros_ec_device *ec;
2862306a36Sopenharmony_ci	struct iio_dev *indio_dev;
2962306a36Sopenharmony_ci	struct mutex lock;
3062306a36Sopenharmony_ci	struct notifier_block notifier;
3162306a36Sopenharmony_ci	int last_proximity;
3262306a36Sopenharmony_ci	bool enabled;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic const struct iio_event_spec cros_ec_mkbp_proximity_events[] = {
3662306a36Sopenharmony_ci	{
3762306a36Sopenharmony_ci		.type = IIO_EV_TYPE_THRESH,
3862306a36Sopenharmony_ci		.dir = IIO_EV_DIR_EITHER,
3962306a36Sopenharmony_ci		.mask_separate = BIT(IIO_EV_INFO_ENABLE),
4062306a36Sopenharmony_ci	},
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic const struct iio_chan_spec cros_ec_mkbp_proximity_chan_spec[] = {
4462306a36Sopenharmony_ci	{
4562306a36Sopenharmony_ci		.type = IIO_PROXIMITY,
4662306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
4762306a36Sopenharmony_ci		.event_spec = cros_ec_mkbp_proximity_events,
4862306a36Sopenharmony_ci		.num_event_specs = ARRAY_SIZE(cros_ec_mkbp_proximity_events),
4962306a36Sopenharmony_ci	},
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_parse_state(const void *data)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	u32 switches = get_unaligned_le32(data);
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci	return !!(switches & BIT(EC_MKBP_FRONT_PROXIMITY));
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_query(struct cros_ec_device *ec_dev,
6062306a36Sopenharmony_ci					int *state)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct {
6362306a36Sopenharmony_ci		struct cros_ec_command msg;
6462306a36Sopenharmony_ci		union {
6562306a36Sopenharmony_ci			struct ec_params_mkbp_info params;
6662306a36Sopenharmony_ci			u32 switches;
6762306a36Sopenharmony_ci		};
6862306a36Sopenharmony_ci	} __packed buf = { };
6962306a36Sopenharmony_ci	struct ec_params_mkbp_info *params = &buf.params;
7062306a36Sopenharmony_ci	struct cros_ec_command *msg = &buf.msg;
7162306a36Sopenharmony_ci	u32 *switches = &buf.switches;
7262306a36Sopenharmony_ci	size_t insize = sizeof(*switches);
7362306a36Sopenharmony_ci	int ret;
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	msg->command = EC_CMD_MKBP_INFO;
7662306a36Sopenharmony_ci	msg->version = 1;
7762306a36Sopenharmony_ci	msg->outsize = sizeof(*params);
7862306a36Sopenharmony_ci	msg->insize = insize;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	params->info_type = EC_MKBP_INFO_CURRENT;
8162306a36Sopenharmony_ci	params->event_type = EC_MKBP_EVENT_SWITCH;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	ret = cros_ec_cmd_xfer_status(ec_dev, msg);
8462306a36Sopenharmony_ci	if (ret < 0)
8562306a36Sopenharmony_ci		return ret;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	if (ret != insize) {
8862306a36Sopenharmony_ci		dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n", ret,
8962306a36Sopenharmony_ci			 insize);
9062306a36Sopenharmony_ci		return -EPROTO;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	*state = cros_ec_mkbp_proximity_parse_state(switches);
9462306a36Sopenharmony_ci	return IIO_VAL_INT;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic void cros_ec_mkbp_proximity_push_event(struct cros_ec_mkbp_proximity_data *data, int state)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	s64 timestamp;
10062306a36Sopenharmony_ci	u64 ev;
10162306a36Sopenharmony_ci	int dir;
10262306a36Sopenharmony_ci	struct iio_dev *indio_dev = data->indio_dev;
10362306a36Sopenharmony_ci	struct cros_ec_device *ec = data->ec;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	mutex_lock(&data->lock);
10662306a36Sopenharmony_ci	if (state != data->last_proximity) {
10762306a36Sopenharmony_ci		if (data->enabled) {
10862306a36Sopenharmony_ci			timestamp = ktime_to_ns(ec->last_event_time);
10962306a36Sopenharmony_ci			if (iio_device_get_clock(indio_dev) != CLOCK_BOOTTIME)
11062306a36Sopenharmony_ci				timestamp = iio_get_time_ns(indio_dev);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci			dir = state ? IIO_EV_DIR_FALLING : IIO_EV_DIR_RISING;
11362306a36Sopenharmony_ci			ev = IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,
11462306a36Sopenharmony_ci						  IIO_EV_TYPE_THRESH, dir);
11562306a36Sopenharmony_ci			iio_push_event(indio_dev, ev, timestamp);
11662306a36Sopenharmony_ci		}
11762306a36Sopenharmony_ci		data->last_proximity = state;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci	mutex_unlock(&data->lock);
12062306a36Sopenharmony_ci}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_notify(struct notifier_block *nb,
12362306a36Sopenharmony_ci					 unsigned long queued_during_suspend,
12462306a36Sopenharmony_ci					 void *_ec)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data;
12762306a36Sopenharmony_ci	struct cros_ec_device *ec = _ec;
12862306a36Sopenharmony_ci	u8 event_type = ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK;
12962306a36Sopenharmony_ci	void *switches;
13062306a36Sopenharmony_ci	int state;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	if (event_type == EC_MKBP_EVENT_SWITCH) {
13362306a36Sopenharmony_ci		data = container_of(nb, struct cros_ec_mkbp_proximity_data,
13462306a36Sopenharmony_ci				    notifier);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci		switches = &ec->event_data.data.switches;
13762306a36Sopenharmony_ci		state = cros_ec_mkbp_proximity_parse_state(switches);
13862306a36Sopenharmony_ci		cros_ec_mkbp_proximity_push_event(data, state);
13962306a36Sopenharmony_ci	}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return NOTIFY_OK;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_read_raw(struct iio_dev *indio_dev,
14562306a36Sopenharmony_ci			   const struct iio_chan_spec *chan, int *val,
14662306a36Sopenharmony_ci			   int *val2, long mask)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
14962306a36Sopenharmony_ci	struct cros_ec_device *ec = data->ec;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	if (chan->type == IIO_PROXIMITY && mask == IIO_CHAN_INFO_RAW)
15262306a36Sopenharmony_ci		return cros_ec_mkbp_proximity_query(ec, val);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	return -EINVAL;
15562306a36Sopenharmony_ci}
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_read_event_config(struct iio_dev *indio_dev,
15862306a36Sopenharmony_ci				    const struct iio_chan_spec *chan,
15962306a36Sopenharmony_ci				    enum iio_event_type type,
16062306a36Sopenharmony_ci				    enum iio_event_direction dir)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	return data->enabled;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_write_event_config(struct iio_dev *indio_dev,
16862306a36Sopenharmony_ci				     const struct iio_chan_spec *chan,
16962306a36Sopenharmony_ci				     enum iio_event_type type,
17062306a36Sopenharmony_ci				     enum iio_event_direction dir, int state)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data = iio_priv(indio_dev);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	mutex_lock(&data->lock);
17562306a36Sopenharmony_ci	data->enabled = state;
17662306a36Sopenharmony_ci	mutex_unlock(&data->lock);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	return 0;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic const struct iio_info cros_ec_mkbp_proximity_info = {
18262306a36Sopenharmony_ci	.read_raw = cros_ec_mkbp_proximity_read_raw,
18362306a36Sopenharmony_ci	.read_event_config = cros_ec_mkbp_proximity_read_event_config,
18462306a36Sopenharmony_ci	.write_event_config = cros_ec_mkbp_proximity_write_event_config,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_resume(struct device *dev)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data = dev_get_drvdata(dev);
19062306a36Sopenharmony_ci	struct cros_ec_device *ec = data->ec;
19162306a36Sopenharmony_ci	int ret, state;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	ret = cros_ec_mkbp_proximity_query(ec, &state);
19462306a36Sopenharmony_ci	if (ret < 0) {
19562306a36Sopenharmony_ci		dev_warn(dev, "failed to fetch proximity state on resume: %d\n",
19662306a36Sopenharmony_ci			 ret);
19762306a36Sopenharmony_ci	} else {
19862306a36Sopenharmony_ci		cros_ec_mkbp_proximity_push_event(data, state);
19962306a36Sopenharmony_ci	}
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return 0;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_cistatic DEFINE_SIMPLE_DEV_PM_OPS(cros_ec_mkbp_proximity_pm_ops, NULL,
20562306a36Sopenharmony_ci				cros_ec_mkbp_proximity_resume);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_probe(struct platform_device *pdev)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
21062306a36Sopenharmony_ci	struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
21162306a36Sopenharmony_ci	struct iio_dev *indio_dev;
21262306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data;
21362306a36Sopenharmony_ci	int ret;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
21662306a36Sopenharmony_ci	if (!indio_dev)
21762306a36Sopenharmony_ci		return -ENOMEM;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	data = iio_priv(indio_dev);
22062306a36Sopenharmony_ci	data->ec = ec;
22162306a36Sopenharmony_ci	data->indio_dev = indio_dev;
22262306a36Sopenharmony_ci	data->last_proximity = -1; /* Unknown to start */
22362306a36Sopenharmony_ci	mutex_init(&data->lock);
22462306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	indio_dev->name = dev->driver->name;
22762306a36Sopenharmony_ci	indio_dev->info = &cros_ec_mkbp_proximity_info;
22862306a36Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
22962306a36Sopenharmony_ci	indio_dev->channels = cros_ec_mkbp_proximity_chan_spec;
23062306a36Sopenharmony_ci	indio_dev->num_channels = ARRAY_SIZE(cros_ec_mkbp_proximity_chan_spec);
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	ret = devm_iio_device_register(dev, indio_dev);
23362306a36Sopenharmony_ci	if (ret)
23462306a36Sopenharmony_ci		return ret;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	data->notifier.notifier_call = cros_ec_mkbp_proximity_notify;
23762306a36Sopenharmony_ci	blocking_notifier_chain_register(&ec->event_notifier, &data->notifier);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	return 0;
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_cistatic int cros_ec_mkbp_proximity_remove(struct platform_device *pdev)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	struct cros_ec_mkbp_proximity_data *data = platform_get_drvdata(pdev);
24562306a36Sopenharmony_ci	struct cros_ec_device *ec = data->ec;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	blocking_notifier_chain_unregister(&ec->event_notifier,
24862306a36Sopenharmony_ci					   &data->notifier);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	return 0;
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic const struct of_device_id cros_ec_mkbp_proximity_of_match[] = {
25462306a36Sopenharmony_ci	{ .compatible = "google,cros-ec-mkbp-proximity" },
25562306a36Sopenharmony_ci	{}
25662306a36Sopenharmony_ci};
25762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, cros_ec_mkbp_proximity_of_match);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_cistatic struct platform_driver cros_ec_mkbp_proximity_driver = {
26062306a36Sopenharmony_ci	.driver = {
26162306a36Sopenharmony_ci		.name = "cros-ec-mkbp-proximity",
26262306a36Sopenharmony_ci		.of_match_table = cros_ec_mkbp_proximity_of_match,
26362306a36Sopenharmony_ci		.pm = pm_sleep_ptr(&cros_ec_mkbp_proximity_pm_ops),
26462306a36Sopenharmony_ci	},
26562306a36Sopenharmony_ci	.probe = cros_ec_mkbp_proximity_probe,
26662306a36Sopenharmony_ci	.remove = cros_ec_mkbp_proximity_remove,
26762306a36Sopenharmony_ci};
26862306a36Sopenharmony_cimodule_platform_driver(cros_ec_mkbp_proximity_driver);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
27162306a36Sopenharmony_ciMODULE_DESCRIPTION("ChromeOS EC MKBP proximity sensor driver");
272