162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Plantower PMS7003 particulate matter sensor driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <asm/unaligned.h>
962306a36Sopenharmony_ci#include <linux/completion.h>
1062306a36Sopenharmony_ci#include <linux/device.h>
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/iio/buffer.h>
1362306a36Sopenharmony_ci#include <linux/iio/iio.h>
1462306a36Sopenharmony_ci#include <linux/iio/trigger_consumer.h>
1562306a36Sopenharmony_ci#include <linux/iio/triggered_buffer.h>
1662306a36Sopenharmony_ci#include <linux/jiffies.h>
1762306a36Sopenharmony_ci#include <linux/kernel.h>
1862306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1962306a36Sopenharmony_ci#include <linux/module.h>
2062306a36Sopenharmony_ci#include <linux/mutex.h>
2162306a36Sopenharmony_ci#include <linux/serdev.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define PMS7003_DRIVER_NAME "pms7003"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define PMS7003_MAGIC 0x424d
2662306a36Sopenharmony_ci/* last 2 data bytes hold frame checksum */
2762306a36Sopenharmony_ci#define PMS7003_MAX_DATA_LENGTH 28
2862306a36Sopenharmony_ci#define PMS7003_CHECKSUM_LENGTH 2
2962306a36Sopenharmony_ci#define PMS7003_PM10_OFFSET 10
3062306a36Sopenharmony_ci#define PMS7003_PM2P5_OFFSET 8
3162306a36Sopenharmony_ci#define PMS7003_PM1_OFFSET 6
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define PMS7003_TIMEOUT msecs_to_jiffies(6000)
3462306a36Sopenharmony_ci#define PMS7003_CMD_LENGTH 7
3562306a36Sopenharmony_ci#define PMS7003_PM_MAX 1000
3662306a36Sopenharmony_ci#define PMS7003_PM_MIN 0
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cienum {
3962306a36Sopenharmony_ci	PM1,
4062306a36Sopenharmony_ci	PM2P5,
4162306a36Sopenharmony_ci	PM10,
4262306a36Sopenharmony_ci};
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cienum pms7003_cmd {
4562306a36Sopenharmony_ci	CMD_WAKEUP,
4662306a36Sopenharmony_ci	CMD_ENTER_PASSIVE_MODE,
4762306a36Sopenharmony_ci	CMD_READ_PASSIVE,
4862306a36Sopenharmony_ci	CMD_SLEEP,
4962306a36Sopenharmony_ci};
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci/*
5262306a36Sopenharmony_ci * commands have following format:
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * +------+------+-----+------+-----+-----------+-----------+
5562306a36Sopenharmony_ci * | 0x42 | 0x4d | cmd | 0x00 | arg | cksum msb | cksum lsb |
5662306a36Sopenharmony_ci * +------+------+-----+------+-----+-----------+-----------+
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_cistatic const u8 pms7003_cmd_tbl[][PMS7003_CMD_LENGTH] = {
5962306a36Sopenharmony_ci	[CMD_WAKEUP] = { 0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74 },
6062306a36Sopenharmony_ci	[CMD_ENTER_PASSIVE_MODE] = { 0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70 },
6162306a36Sopenharmony_ci	[CMD_READ_PASSIVE] = { 0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71 },
6262306a36Sopenharmony_ci	[CMD_SLEEP] = { 0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73 },
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistruct pms7003_frame {
6662306a36Sopenharmony_ci	u8 data[PMS7003_MAX_DATA_LENGTH];
6762306a36Sopenharmony_ci	u16 expected_length;
6862306a36Sopenharmony_ci	u16 length;
6962306a36Sopenharmony_ci};
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_cistruct pms7003_state {
7262306a36Sopenharmony_ci	struct serdev_device *serdev;
7362306a36Sopenharmony_ci	struct pms7003_frame frame;
7462306a36Sopenharmony_ci	struct completion frame_ready;
7562306a36Sopenharmony_ci	struct mutex lock; /* must be held whenever state gets touched */
7662306a36Sopenharmony_ci	/* Used to construct scan to push to the IIO buffer */
7762306a36Sopenharmony_ci	struct {
7862306a36Sopenharmony_ci		u16 data[3]; /* PM1, PM2P5, PM10 */
7962306a36Sopenharmony_ci		s64 ts;
8062306a36Sopenharmony_ci	} scan;
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic int pms7003_do_cmd(struct pms7003_state *state, enum pms7003_cmd cmd)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	int ret;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	ret = serdev_device_write(state->serdev, pms7003_cmd_tbl[cmd],
8862306a36Sopenharmony_ci				  PMS7003_CMD_LENGTH, PMS7003_TIMEOUT);
8962306a36Sopenharmony_ci	if (ret < PMS7003_CMD_LENGTH)
9062306a36Sopenharmony_ci		return ret < 0 ? ret : -EIO;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	ret = wait_for_completion_interruptible_timeout(&state->frame_ready,
9362306a36Sopenharmony_ci							PMS7003_TIMEOUT);
9462306a36Sopenharmony_ci	if (!ret)
9562306a36Sopenharmony_ci		ret = -ETIMEDOUT;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return ret < 0 ? ret : 0;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic u16 pms7003_get_pm(const u8 *data)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	return clamp_val(get_unaligned_be16(data),
10362306a36Sopenharmony_ci			 PMS7003_PM_MIN, PMS7003_PM_MAX);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic irqreturn_t pms7003_trigger_handler(int irq, void *p)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct iio_poll_func *pf = p;
10962306a36Sopenharmony_ci	struct iio_dev *indio_dev = pf->indio_dev;
11062306a36Sopenharmony_ci	struct pms7003_state *state = iio_priv(indio_dev);
11162306a36Sopenharmony_ci	struct pms7003_frame *frame = &state->frame;
11262306a36Sopenharmony_ci	int ret;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	mutex_lock(&state->lock);
11562306a36Sopenharmony_ci	ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
11662306a36Sopenharmony_ci	if (ret) {
11762306a36Sopenharmony_ci		mutex_unlock(&state->lock);
11862306a36Sopenharmony_ci		goto err;
11962306a36Sopenharmony_ci	}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	state->scan.data[PM1] =
12262306a36Sopenharmony_ci		pms7003_get_pm(frame->data + PMS7003_PM1_OFFSET);
12362306a36Sopenharmony_ci	state->scan.data[PM2P5] =
12462306a36Sopenharmony_ci		pms7003_get_pm(frame->data + PMS7003_PM2P5_OFFSET);
12562306a36Sopenharmony_ci	state->scan.data[PM10] =
12662306a36Sopenharmony_ci		pms7003_get_pm(frame->data + PMS7003_PM10_OFFSET);
12762306a36Sopenharmony_ci	mutex_unlock(&state->lock);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	iio_push_to_buffers_with_timestamp(indio_dev, &state->scan,
13062306a36Sopenharmony_ci					   iio_get_time_ns(indio_dev));
13162306a36Sopenharmony_cierr:
13262306a36Sopenharmony_ci	iio_trigger_notify_done(indio_dev->trig);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	return IRQ_HANDLED;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int pms7003_read_raw(struct iio_dev *indio_dev,
13862306a36Sopenharmony_ci			    struct iio_chan_spec const *chan,
13962306a36Sopenharmony_ci			    int *val, int *val2, long mask)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct pms7003_state *state = iio_priv(indio_dev);
14262306a36Sopenharmony_ci	struct pms7003_frame *frame = &state->frame;
14362306a36Sopenharmony_ci	int ret;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	switch (mask) {
14662306a36Sopenharmony_ci	case IIO_CHAN_INFO_PROCESSED:
14762306a36Sopenharmony_ci		switch (chan->type) {
14862306a36Sopenharmony_ci		case IIO_MASSCONCENTRATION:
14962306a36Sopenharmony_ci			mutex_lock(&state->lock);
15062306a36Sopenharmony_ci			ret = pms7003_do_cmd(state, CMD_READ_PASSIVE);
15162306a36Sopenharmony_ci			if (ret) {
15262306a36Sopenharmony_ci				mutex_unlock(&state->lock);
15362306a36Sopenharmony_ci				return ret;
15462306a36Sopenharmony_ci			}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci			*val = pms7003_get_pm(frame->data + chan->address);
15762306a36Sopenharmony_ci			mutex_unlock(&state->lock);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci			return IIO_VAL_INT;
16062306a36Sopenharmony_ci		default:
16162306a36Sopenharmony_ci			return -EINVAL;
16262306a36Sopenharmony_ci		}
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	return -EINVAL;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic const struct iio_info pms7003_info = {
16962306a36Sopenharmony_ci	.read_raw = pms7003_read_raw,
17062306a36Sopenharmony_ci};
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci#define PMS7003_CHAN(_index, _mod, _addr) { \
17362306a36Sopenharmony_ci	.type = IIO_MASSCONCENTRATION, \
17462306a36Sopenharmony_ci	.modified = 1, \
17562306a36Sopenharmony_ci	.channel2 = IIO_MOD_ ## _mod, \
17662306a36Sopenharmony_ci	.address = _addr, \
17762306a36Sopenharmony_ci	.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \
17862306a36Sopenharmony_ci	.scan_index = _index, \
17962306a36Sopenharmony_ci	.scan_type = { \
18062306a36Sopenharmony_ci		.sign = 'u', \
18162306a36Sopenharmony_ci		.realbits = 10, \
18262306a36Sopenharmony_ci		.storagebits = 16, \
18362306a36Sopenharmony_ci		.endianness = IIO_CPU, \
18462306a36Sopenharmony_ci	}, \
18562306a36Sopenharmony_ci}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cistatic const struct iio_chan_spec pms7003_channels[] = {
18862306a36Sopenharmony_ci	PMS7003_CHAN(0, PM1, PMS7003_PM1_OFFSET),
18962306a36Sopenharmony_ci	PMS7003_CHAN(1, PM2P5, PMS7003_PM2P5_OFFSET),
19062306a36Sopenharmony_ci	PMS7003_CHAN(2, PM10, PMS7003_PM10_OFFSET),
19162306a36Sopenharmony_ci	IIO_CHAN_SOFT_TIMESTAMP(3),
19262306a36Sopenharmony_ci};
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic u16 pms7003_calc_checksum(struct pms7003_frame *frame)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	u16 checksum = (PMS7003_MAGIC >> 8) + (u8)(PMS7003_MAGIC & 0xff) +
19762306a36Sopenharmony_ci		       (frame->length >> 8) + (u8)frame->length;
19862306a36Sopenharmony_ci	int i;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	for (i = 0; i < frame->length - PMS7003_CHECKSUM_LENGTH; i++)
20162306a36Sopenharmony_ci		checksum += frame->data[i];
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return checksum;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic bool pms7003_frame_is_okay(struct pms7003_frame *frame)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	int offset = frame->length - PMS7003_CHECKSUM_LENGTH;
20962306a36Sopenharmony_ci	u16 checksum = get_unaligned_be16(frame->data + offset);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	return checksum == pms7003_calc_checksum(frame);
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic int pms7003_receive_buf(struct serdev_device *serdev,
21562306a36Sopenharmony_ci			       const unsigned char *buf, size_t size)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
21862306a36Sopenharmony_ci	struct pms7003_state *state = iio_priv(indio_dev);
21962306a36Sopenharmony_ci	struct pms7003_frame *frame = &state->frame;
22062306a36Sopenharmony_ci	int num;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	if (!frame->expected_length) {
22362306a36Sopenharmony_ci		u16 magic;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci		/* wait for SOF and data length */
22662306a36Sopenharmony_ci		if (size < 4)
22762306a36Sopenharmony_ci			return 0;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci		magic = get_unaligned_be16(buf);
23062306a36Sopenharmony_ci		if (magic != PMS7003_MAGIC)
23162306a36Sopenharmony_ci			return 2;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci		num = get_unaligned_be16(buf + 2);
23462306a36Sopenharmony_ci		if (num <= PMS7003_MAX_DATA_LENGTH) {
23562306a36Sopenharmony_ci			frame->expected_length = num;
23662306a36Sopenharmony_ci			frame->length = 0;
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		return 4;
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	num = min(size, (size_t)(frame->expected_length - frame->length));
24362306a36Sopenharmony_ci	memcpy(frame->data + frame->length, buf, num);
24462306a36Sopenharmony_ci	frame->length += num;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (frame->length == frame->expected_length) {
24762306a36Sopenharmony_ci		if (pms7003_frame_is_okay(frame))
24862306a36Sopenharmony_ci			complete(&state->frame_ready);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci		frame->expected_length = 0;
25162306a36Sopenharmony_ci	}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return num;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_cistatic const struct serdev_device_ops pms7003_serdev_ops = {
25762306a36Sopenharmony_ci	.receive_buf = pms7003_receive_buf,
25862306a36Sopenharmony_ci	.write_wakeup = serdev_device_write_wakeup,
25962306a36Sopenharmony_ci};
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_cistatic void pms7003_stop(void *data)
26262306a36Sopenharmony_ci{
26362306a36Sopenharmony_ci	struct pms7003_state *state = data;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	pms7003_do_cmd(state, CMD_SLEEP);
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic const unsigned long pms7003_scan_masks[] = { 0x07, 0x00 };
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic int pms7003_probe(struct serdev_device *serdev)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct pms7003_state *state;
27362306a36Sopenharmony_ci	struct iio_dev *indio_dev;
27462306a36Sopenharmony_ci	int ret;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	indio_dev = devm_iio_device_alloc(&serdev->dev, sizeof(*state));
27762306a36Sopenharmony_ci	if (!indio_dev)
27862306a36Sopenharmony_ci		return -ENOMEM;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	state = iio_priv(indio_dev);
28162306a36Sopenharmony_ci	serdev_device_set_drvdata(serdev, indio_dev);
28262306a36Sopenharmony_ci	state->serdev = serdev;
28362306a36Sopenharmony_ci	indio_dev->info = &pms7003_info;
28462306a36Sopenharmony_ci	indio_dev->name = PMS7003_DRIVER_NAME;
28562306a36Sopenharmony_ci	indio_dev->channels = pms7003_channels;
28662306a36Sopenharmony_ci	indio_dev->num_channels = ARRAY_SIZE(pms7003_channels);
28762306a36Sopenharmony_ci	indio_dev->modes = INDIO_DIRECT_MODE;
28862306a36Sopenharmony_ci	indio_dev->available_scan_masks = pms7003_scan_masks;
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	mutex_init(&state->lock);
29162306a36Sopenharmony_ci	init_completion(&state->frame_ready);
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	serdev_device_set_client_ops(serdev, &pms7003_serdev_ops);
29462306a36Sopenharmony_ci	ret = devm_serdev_device_open(&serdev->dev, serdev);
29562306a36Sopenharmony_ci	if (ret)
29662306a36Sopenharmony_ci		return ret;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	serdev_device_set_baudrate(serdev, 9600);
29962306a36Sopenharmony_ci	serdev_device_set_flow_control(serdev, false);
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
30262306a36Sopenharmony_ci	if (ret)
30362306a36Sopenharmony_ci		return ret;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	ret = pms7003_do_cmd(state, CMD_WAKEUP);
30662306a36Sopenharmony_ci	if (ret) {
30762306a36Sopenharmony_ci		dev_err(&serdev->dev, "failed to wakeup sensor\n");
30862306a36Sopenharmony_ci		return ret;
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	ret = pms7003_do_cmd(state, CMD_ENTER_PASSIVE_MODE);
31262306a36Sopenharmony_ci	if (ret) {
31362306a36Sopenharmony_ci		dev_err(&serdev->dev, "failed to enter passive mode\n");
31462306a36Sopenharmony_ci		return ret;
31562306a36Sopenharmony_ci	}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	ret = devm_add_action_or_reset(&serdev->dev, pms7003_stop, state);
31862306a36Sopenharmony_ci	if (ret)
31962306a36Sopenharmony_ci		return ret;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	ret = devm_iio_triggered_buffer_setup(&serdev->dev, indio_dev, NULL,
32262306a36Sopenharmony_ci					      pms7003_trigger_handler, NULL);
32362306a36Sopenharmony_ci	if (ret)
32462306a36Sopenharmony_ci		return ret;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	return devm_iio_device_register(&serdev->dev, indio_dev);
32762306a36Sopenharmony_ci}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_cistatic const struct of_device_id pms7003_of_match[] = {
33062306a36Sopenharmony_ci	{ .compatible = "plantower,pms1003" },
33162306a36Sopenharmony_ci	{ .compatible = "plantower,pms3003" },
33262306a36Sopenharmony_ci	{ .compatible = "plantower,pms5003" },
33362306a36Sopenharmony_ci	{ .compatible = "plantower,pms6003" },
33462306a36Sopenharmony_ci	{ .compatible = "plantower,pms7003" },
33562306a36Sopenharmony_ci	{ .compatible = "plantower,pmsa003" },
33662306a36Sopenharmony_ci	{ }
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pms7003_of_match);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cistatic struct serdev_device_driver pms7003_driver = {
34162306a36Sopenharmony_ci	.driver = {
34262306a36Sopenharmony_ci		.name = PMS7003_DRIVER_NAME,
34362306a36Sopenharmony_ci		.of_match_table = pms7003_of_match,
34462306a36Sopenharmony_ci	},
34562306a36Sopenharmony_ci	.probe = pms7003_probe,
34662306a36Sopenharmony_ci};
34762306a36Sopenharmony_cimodule_serdev_device_driver(pms7003_driver);
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ciMODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>");
35062306a36Sopenharmony_ciMODULE_DESCRIPTION("Plantower PMS7003 particulate matter sensor driver");
35162306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
352