18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Common library for ADIS16XXX devices
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2012 Analog Devices Inc.
68c2ecf20Sopenharmony_ci *   Author: Lars-Peter Clausen <lars@metafoo.de>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/export.h>
108c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
118c2ecf20Sopenharmony_ci#include <linux/mutex.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
148c2ecf20Sopenharmony_ci#include <linux/slab.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <linux/iio/iio.h>
178c2ecf20Sopenharmony_ci#include <linux/iio/buffer.h>
188c2ecf20Sopenharmony_ci#include <linux/iio/trigger_consumer.h>
198c2ecf20Sopenharmony_ci#include <linux/iio/triggered_buffer.h>
208c2ecf20Sopenharmony_ci#include <linux/iio/imu/adis.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistatic int adis_update_scan_mode_burst(struct iio_dev *indio_dev,
238c2ecf20Sopenharmony_ci				       const unsigned long *scan_mask)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	struct adis *adis = iio_device_get_drvdata(indio_dev);
268c2ecf20Sopenharmony_ci	unsigned int burst_length, burst_max_length;
278c2ecf20Sopenharmony_ci	u8 *tx;
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	burst_length = adis->data->burst_len + adis->burst_extra_len;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	if (adis->data->burst_max_len)
328c2ecf20Sopenharmony_ci		burst_max_length = adis->data->burst_max_len;
338c2ecf20Sopenharmony_ci	else
348c2ecf20Sopenharmony_ci		burst_max_length = burst_length;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	adis->xfer = kcalloc(2, sizeof(*adis->xfer), GFP_KERNEL);
378c2ecf20Sopenharmony_ci	if (!adis->xfer)
388c2ecf20Sopenharmony_ci		return -ENOMEM;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	adis->buffer = kzalloc(burst_max_length + sizeof(u16), GFP_KERNEL);
418c2ecf20Sopenharmony_ci	if (!adis->buffer) {
428c2ecf20Sopenharmony_ci		kfree(adis->xfer);
438c2ecf20Sopenharmony_ci		adis->xfer = NULL;
448c2ecf20Sopenharmony_ci		return -ENOMEM;
458c2ecf20Sopenharmony_ci	}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	tx = adis->buffer + burst_max_length;
488c2ecf20Sopenharmony_ci	tx[0] = ADIS_READ_REG(adis->data->burst_reg_cmd);
498c2ecf20Sopenharmony_ci	tx[1] = 0;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	adis->xfer[0].tx_buf = tx;
528c2ecf20Sopenharmony_ci	adis->xfer[0].bits_per_word = 8;
538c2ecf20Sopenharmony_ci	adis->xfer[0].len = 2;
548c2ecf20Sopenharmony_ci	adis->xfer[1].rx_buf = adis->buffer;
558c2ecf20Sopenharmony_ci	adis->xfer[1].bits_per_word = 8;
568c2ecf20Sopenharmony_ci	adis->xfer[1].len = burst_length;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	spi_message_init(&adis->msg);
598c2ecf20Sopenharmony_ci	spi_message_add_tail(&adis->xfer[0], &adis->msg);
608c2ecf20Sopenharmony_ci	spi_message_add_tail(&adis->xfer[1], &adis->msg);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return 0;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ciint adis_update_scan_mode(struct iio_dev *indio_dev,
668c2ecf20Sopenharmony_ci			  const unsigned long *scan_mask)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct adis *adis = iio_device_get_drvdata(indio_dev);
698c2ecf20Sopenharmony_ci	const struct iio_chan_spec *chan;
708c2ecf20Sopenharmony_ci	unsigned int scan_count;
718c2ecf20Sopenharmony_ci	unsigned int i, j;
728c2ecf20Sopenharmony_ci	__be16 *tx, *rx;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	kfree(adis->xfer);
758c2ecf20Sopenharmony_ci	kfree(adis->buffer);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci	if (adis->data->burst_len)
788c2ecf20Sopenharmony_ci		return adis_update_scan_mode_burst(indio_dev, scan_mask);
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	scan_count = indio_dev->scan_bytes / 2;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	adis->xfer = kcalloc(scan_count + 1, sizeof(*adis->xfer), GFP_KERNEL);
838c2ecf20Sopenharmony_ci	if (!adis->xfer)
848c2ecf20Sopenharmony_ci		return -ENOMEM;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	adis->buffer = kcalloc(indio_dev->scan_bytes, 2, GFP_KERNEL);
878c2ecf20Sopenharmony_ci	if (!adis->buffer) {
888c2ecf20Sopenharmony_ci		kfree(adis->xfer);
898c2ecf20Sopenharmony_ci		adis->xfer = NULL;
908c2ecf20Sopenharmony_ci		return -ENOMEM;
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	rx = adis->buffer;
948c2ecf20Sopenharmony_ci	tx = rx + scan_count;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	spi_message_init(&adis->msg);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	for (j = 0; j <= scan_count; j++) {
998c2ecf20Sopenharmony_ci		adis->xfer[j].bits_per_word = 8;
1008c2ecf20Sopenharmony_ci		if (j != scan_count)
1018c2ecf20Sopenharmony_ci			adis->xfer[j].cs_change = 1;
1028c2ecf20Sopenharmony_ci		adis->xfer[j].len = 2;
1038c2ecf20Sopenharmony_ci		adis->xfer[j].delay.value = adis->data->read_delay;
1048c2ecf20Sopenharmony_ci		adis->xfer[j].delay.unit = SPI_DELAY_UNIT_USECS;
1058c2ecf20Sopenharmony_ci		if (j < scan_count)
1068c2ecf20Sopenharmony_ci			adis->xfer[j].tx_buf = &tx[j];
1078c2ecf20Sopenharmony_ci		if (j >= 1)
1088c2ecf20Sopenharmony_ci			adis->xfer[j].rx_buf = &rx[j - 1];
1098c2ecf20Sopenharmony_ci		spi_message_add_tail(&adis->xfer[j], &adis->msg);
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	chan = indio_dev->channels;
1138c2ecf20Sopenharmony_ci	for (i = 0; i < indio_dev->num_channels; i++, chan++) {
1148c2ecf20Sopenharmony_ci		if (!test_bit(chan->scan_index, scan_mask))
1158c2ecf20Sopenharmony_ci			continue;
1168c2ecf20Sopenharmony_ci		if (chan->scan_type.storagebits == 32)
1178c2ecf20Sopenharmony_ci			*tx++ = cpu_to_be16((chan->address + 2) << 8);
1188c2ecf20Sopenharmony_ci		*tx++ = cpu_to_be16(chan->address << 8);
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	return 0;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(adis_update_scan_mode, IIO_ADISLIB);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic irqreturn_t adis_trigger_handler(int irq, void *p)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct iio_poll_func *pf = p;
1288c2ecf20Sopenharmony_ci	struct iio_dev *indio_dev = pf->indio_dev;
1298c2ecf20Sopenharmony_ci	struct adis *adis = iio_device_get_drvdata(indio_dev);
1308c2ecf20Sopenharmony_ci	int ret;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (adis->data->has_paging) {
1338c2ecf20Sopenharmony_ci		mutex_lock(&adis->state_lock);
1348c2ecf20Sopenharmony_ci		if (adis->current_page != 0) {
1358c2ecf20Sopenharmony_ci			adis->tx[0] = ADIS_WRITE_REG(ADIS_REG_PAGE_ID);
1368c2ecf20Sopenharmony_ci			adis->tx[1] = 0;
1378c2ecf20Sopenharmony_ci			spi_write(adis->spi, adis->tx, 2);
1388c2ecf20Sopenharmony_ci		}
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	ret = spi_sync(adis->spi, &adis->msg);
1428c2ecf20Sopenharmony_ci	if (ret)
1438c2ecf20Sopenharmony_ci		dev_err(&adis->spi->dev, "Failed to read data: %d", ret);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	if (adis->data->has_paging) {
1478c2ecf20Sopenharmony_ci		adis->current_page = 0;
1488c2ecf20Sopenharmony_ci		mutex_unlock(&adis->state_lock);
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	iio_push_to_buffers_with_timestamp(indio_dev, adis->buffer,
1528c2ecf20Sopenharmony_ci					   pf->timestamp);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	iio_trigger_notify_done(indio_dev->trig);
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic void adis_buffer_cleanup(void *arg)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	struct adis *adis = arg;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	kfree(adis->buffer);
1648c2ecf20Sopenharmony_ci	kfree(adis->xfer);
1658c2ecf20Sopenharmony_ci}
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci/**
1688c2ecf20Sopenharmony_ci * devm_adis_setup_buffer_and_trigger() - Sets up buffer and trigger for
1698c2ecf20Sopenharmony_ci *					  the managed adis device
1708c2ecf20Sopenharmony_ci * @adis: The adis device
1718c2ecf20Sopenharmony_ci * @indio_dev: The IIO device
1728c2ecf20Sopenharmony_ci * @trigger_handler: Optional trigger handler, may be NULL.
1738c2ecf20Sopenharmony_ci *
1748c2ecf20Sopenharmony_ci * Returns 0 on success, a negative error code otherwise.
1758c2ecf20Sopenharmony_ci *
1768c2ecf20Sopenharmony_ci * This function sets up the buffer and trigger for a adis devices.  If
1778c2ecf20Sopenharmony_ci * 'trigger_handler' is NULL the default trigger handler will be used. The
1788c2ecf20Sopenharmony_ci * default trigger handler will simply read the registers assigned to the
1798c2ecf20Sopenharmony_ci * currently active channels.
1808c2ecf20Sopenharmony_ci */
1818c2ecf20Sopenharmony_ciint
1828c2ecf20Sopenharmony_cidevm_adis_setup_buffer_and_trigger(struct adis *adis, struct iio_dev *indio_dev,
1838c2ecf20Sopenharmony_ci				   irq_handler_t trigger_handler)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	int ret;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	if (!trigger_handler)
1888c2ecf20Sopenharmony_ci		trigger_handler = adis_trigger_handler;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	ret = devm_iio_triggered_buffer_setup(&adis->spi->dev, indio_dev,
1918c2ecf20Sopenharmony_ci					      &iio_pollfunc_store_time,
1928c2ecf20Sopenharmony_ci					      trigger_handler, NULL);
1938c2ecf20Sopenharmony_ci	if (ret)
1948c2ecf20Sopenharmony_ci		return ret;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	if (adis->spi->irq) {
1978c2ecf20Sopenharmony_ci		ret = devm_adis_probe_trigger(adis, indio_dev);
1988c2ecf20Sopenharmony_ci		if (ret)
1998c2ecf20Sopenharmony_ci			return ret;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return devm_add_action_or_reset(&adis->spi->dev, adis_buffer_cleanup,
2038c2ecf20Sopenharmony_ci					adis);
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(devm_adis_setup_buffer_and_trigger, IIO_ADISLIB);
2068c2ecf20Sopenharmony_ci
207