162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * mpl115.c - Support for Freescale MPL115A pressure/temperature sensor
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2014 Peter Meerwald <pmeerw@pmeerw.net>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * TODO: synchronization with system suspend
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <linux/iio/iio.h>
1262306a36Sopenharmony_ci#include <linux/delay.h>
1362306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "mpl115.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define MPL115_PADC 0x00 /* pressure ADC output value, MSB first, 10 bit */
1862306a36Sopenharmony_ci#define MPL115_TADC 0x02 /* temperature ADC output value, MSB first, 10 bit */
1962306a36Sopenharmony_ci#define MPL115_A0 0x04 /* 12 bit integer, 3 bit fraction */
2062306a36Sopenharmony_ci#define MPL115_B1 0x06 /* 2 bit integer, 13 bit fraction */
2162306a36Sopenharmony_ci#define MPL115_B2 0x08 /* 1 bit integer, 14 bit fraction */
2262306a36Sopenharmony_ci#define MPL115_C12 0x0a /* 0 bit integer, 13 bit fraction */
2362306a36Sopenharmony_ci#define MPL115_CONVERT 0x12 /* convert temperature and pressure */
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistruct mpl115_data {
2662306a36Sopenharmony_ci	struct device *dev;
2762306a36Sopenharmony_ci	struct mutex lock;
2862306a36Sopenharmony_ci	s16 a0;
2962306a36Sopenharmony_ci	s16 b1, b2;
3062306a36Sopenharmony_ci	s16 c12;
3162306a36Sopenharmony_ci	struct gpio_desc *shutdown;
3262306a36Sopenharmony_ci	const struct mpl115_ops *ops;
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int mpl115_request(struct mpl115_data *data)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	int ret = data->ops->write(data->dev, MPL115_CONVERT, 0);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	if (ret < 0)
4062306a36Sopenharmony_ci		return ret;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	usleep_range(3000, 4000);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	return 0;
4562306a36Sopenharmony_ci}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistatic int mpl115_comp_pressure(struct mpl115_data *data, int *val, int *val2)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	int ret;
5062306a36Sopenharmony_ci	u16 padc, tadc;
5162306a36Sopenharmony_ci	int a1, y1, pcomp;
5262306a36Sopenharmony_ci	unsigned kpa;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	mutex_lock(&data->lock);
5562306a36Sopenharmony_ci	ret = mpl115_request(data);
5662306a36Sopenharmony_ci	if (ret < 0)
5762306a36Sopenharmony_ci		goto done;
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_PADC);
6062306a36Sopenharmony_ci	if (ret < 0)
6162306a36Sopenharmony_ci		goto done;
6262306a36Sopenharmony_ci	padc = ret >> 6;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_TADC);
6562306a36Sopenharmony_ci	if (ret < 0)
6662306a36Sopenharmony_ci		goto done;
6762306a36Sopenharmony_ci	tadc = ret >> 6;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	/* see Freescale AN3785 */
7062306a36Sopenharmony_ci	a1 = data->b1 + ((data->c12 * tadc) >> 11);
7162306a36Sopenharmony_ci	y1 = (data->a0 << 10) + a1 * padc;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/* compensated pressure with 4 fractional bits */
7462306a36Sopenharmony_ci	pcomp = (y1 + ((data->b2 * (int) tadc) >> 1)) >> 9;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	kpa = pcomp * (115 - 50) / 1023 + (50 << 4);
7762306a36Sopenharmony_ci	*val = kpa >> 4;
7862306a36Sopenharmony_ci	*val2 = (kpa & 15) * (1000000 >> 4);
7962306a36Sopenharmony_cidone:
8062306a36Sopenharmony_ci	mutex_unlock(&data->lock);
8162306a36Sopenharmony_ci	return ret;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int mpl115_read_temp(struct mpl115_data *data)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	int ret;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	mutex_lock(&data->lock);
8962306a36Sopenharmony_ci	ret = mpl115_request(data);
9062306a36Sopenharmony_ci	if (ret < 0)
9162306a36Sopenharmony_ci		goto done;
9262306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_TADC);
9362306a36Sopenharmony_cidone:
9462306a36Sopenharmony_ci	mutex_unlock(&data->lock);
9562306a36Sopenharmony_ci	return ret;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int mpl115_read_raw(struct iio_dev *indio_dev,
9962306a36Sopenharmony_ci			    struct iio_chan_spec const *chan,
10062306a36Sopenharmony_ci			    int *val, int *val2, long mask)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct mpl115_data *data = iio_priv(indio_dev);
10362306a36Sopenharmony_ci	int ret;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	switch (mask) {
10662306a36Sopenharmony_ci	case IIO_CHAN_INFO_PROCESSED:
10762306a36Sopenharmony_ci		pm_runtime_get_sync(data->dev);
10862306a36Sopenharmony_ci		ret = mpl115_comp_pressure(data, val, val2);
10962306a36Sopenharmony_ci		if (ret < 0)
11062306a36Sopenharmony_ci			return ret;
11162306a36Sopenharmony_ci		pm_runtime_mark_last_busy(data->dev);
11262306a36Sopenharmony_ci		pm_runtime_put_autosuspend(data->dev);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		return IIO_VAL_INT_PLUS_MICRO;
11562306a36Sopenharmony_ci	case IIO_CHAN_INFO_RAW:
11662306a36Sopenharmony_ci		pm_runtime_get_sync(data->dev);
11762306a36Sopenharmony_ci		/* temperature -5.35 C / LSB, 472 LSB is 25 C */
11862306a36Sopenharmony_ci		ret = mpl115_read_temp(data);
11962306a36Sopenharmony_ci		if (ret < 0)
12062306a36Sopenharmony_ci			return ret;
12162306a36Sopenharmony_ci		pm_runtime_mark_last_busy(data->dev);
12262306a36Sopenharmony_ci		pm_runtime_put_autosuspend(data->dev);
12362306a36Sopenharmony_ci		*val = ret >> 6;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci		return IIO_VAL_INT;
12662306a36Sopenharmony_ci	case IIO_CHAN_INFO_OFFSET:
12762306a36Sopenharmony_ci		*val = -605;
12862306a36Sopenharmony_ci		*val2 = 750000;
12962306a36Sopenharmony_ci		return IIO_VAL_INT_PLUS_MICRO;
13062306a36Sopenharmony_ci	case IIO_CHAN_INFO_SCALE:
13162306a36Sopenharmony_ci		*val = -186;
13262306a36Sopenharmony_ci		*val2 = 915888;
13362306a36Sopenharmony_ci		return IIO_VAL_INT_PLUS_MICRO;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci	return -EINVAL;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct iio_chan_spec mpl115_channels[] = {
13962306a36Sopenharmony_ci	{
14062306a36Sopenharmony_ci		.type = IIO_PRESSURE,
14162306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
14262306a36Sopenharmony_ci	},
14362306a36Sopenharmony_ci	{
14462306a36Sopenharmony_ci		.type = IIO_TEMP,
14562306a36Sopenharmony_ci		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
14662306a36Sopenharmony_ci		.info_mask_shared_by_type =
14762306a36Sopenharmony_ci			BIT(IIO_CHAN_INFO_OFFSET) | BIT(IIO_CHAN_INFO_SCALE),
14862306a36Sopenharmony_ci	},
14962306a36Sopenharmony_ci};
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic const struct iio_info mpl115_info = {
15262306a36Sopenharmony_ci	.read_raw = &mpl115_read_raw,
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ciint mpl115_probe(struct device *dev, const char *name,
15662306a36Sopenharmony_ci			const struct mpl115_ops *ops)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	struct mpl115_data *data;
15962306a36Sopenharmony_ci	struct iio_dev *indio_dev;
16062306a36Sopenharmony_ci	int ret;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
16362306a36Sopenharmony_ci	if (!indio_dev)
16462306a36Sopenharmony_ci		return -ENOMEM;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	data = iio_priv(indio_dev);
16762306a36Sopenharmony_ci	data->dev = dev;
16862306a36Sopenharmony_ci	data->ops = ops;
16962306a36Sopenharmony_ci	mutex_init(&data->lock);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	indio_dev->info = &mpl115_info;
17262306a36Sopenharmony_ci	indio_dev->name = name;
17362306a36Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
17462306a36Sopenharmony_ci	indio_dev->channels = mpl115_channels;
17562306a36Sopenharmony_ci	indio_dev->num_channels = ARRAY_SIZE(mpl115_channels);
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	ret = data->ops->init(data->dev);
17862306a36Sopenharmony_ci	if (ret)
17962306a36Sopenharmony_ci		return ret;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	dev_set_drvdata(dev, indio_dev);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_A0);
18462306a36Sopenharmony_ci	if (ret < 0)
18562306a36Sopenharmony_ci		return ret;
18662306a36Sopenharmony_ci	data->a0 = ret;
18762306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_B1);
18862306a36Sopenharmony_ci	if (ret < 0)
18962306a36Sopenharmony_ci		return ret;
19062306a36Sopenharmony_ci	data->b1 = ret;
19162306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_B2);
19262306a36Sopenharmony_ci	if (ret < 0)
19362306a36Sopenharmony_ci		return ret;
19462306a36Sopenharmony_ci	data->b2 = ret;
19562306a36Sopenharmony_ci	ret = data->ops->read(data->dev, MPL115_C12);
19662306a36Sopenharmony_ci	if (ret < 0)
19762306a36Sopenharmony_ci		return ret;
19862306a36Sopenharmony_ci	data->c12 = ret;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	data->shutdown = devm_gpiod_get_optional(dev, "shutdown",
20162306a36Sopenharmony_ci						 GPIOD_OUT_LOW);
20262306a36Sopenharmony_ci	if (IS_ERR(data->shutdown))
20362306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(data->shutdown),
20462306a36Sopenharmony_ci				     "cannot get shutdown gpio\n");
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (data->shutdown) {
20762306a36Sopenharmony_ci		/* Enable runtime PM */
20862306a36Sopenharmony_ci		pm_runtime_get_noresume(dev);
20962306a36Sopenharmony_ci		pm_runtime_set_active(dev);
21062306a36Sopenharmony_ci		pm_runtime_enable(dev);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci		/*
21362306a36Sopenharmony_ci		 * As the device takes 3 ms to come up with a fresh
21462306a36Sopenharmony_ci		 * reading after power-on and 5 ms to actually power-on,
21562306a36Sopenharmony_ci		 * do not shut it down unnecessarily. Set autosuspend to
21662306a36Sopenharmony_ci		 * 2000 ms.
21762306a36Sopenharmony_ci		 */
21862306a36Sopenharmony_ci		pm_runtime_set_autosuspend_delay(dev, 2000);
21962306a36Sopenharmony_ci		pm_runtime_use_autosuspend(dev);
22062306a36Sopenharmony_ci		pm_runtime_put(dev);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		dev_dbg(dev, "low-power mode enabled");
22362306a36Sopenharmony_ci	} else
22462306a36Sopenharmony_ci		dev_dbg(dev, "low-power mode disabled");
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return devm_iio_device_register(dev, indio_dev);
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(mpl115_probe, IIO_MPL115);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int mpl115_runtime_suspend(struct device *dev)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct mpl115_data *data = iio_priv(dev_get_drvdata(dev));
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	gpiod_set_value(data->shutdown, 1);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	return 0;
23762306a36Sopenharmony_ci}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic int mpl115_runtime_resume(struct device *dev)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct mpl115_data *data = iio_priv(dev_get_drvdata(dev));
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	gpiod_set_value(data->shutdown, 0);
24462306a36Sopenharmony_ci	usleep_range(5000, 6000);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return 0;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ciEXPORT_NS_RUNTIME_DEV_PM_OPS(mpl115_dev_pm_ops, mpl115_runtime_suspend,
25062306a36Sopenharmony_ci			  mpl115_runtime_resume, NULL, IIO_MPL115);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ciMODULE_AUTHOR("Peter Meerwald <pmeerw@pmeerw.net>");
25362306a36Sopenharmony_ciMODULE_DESCRIPTION("Freescale MPL115 pressure/temperature driver");
25462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
255