| /* | 
 |  * ad2s1200.c simple support for the ADI Resolver to Digital Converters: | 
 |  * AD2S1200/1205 | 
 |  * | 
 |  * Copyright (c) 2018-2018 David Veenstra <davidjulianveenstra@gmail.com> | 
 |  * Copyright (c) 2010-2010 Analog Devices Inc. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 as | 
 |  * published by the Free Software Foundation. | 
 |  */ | 
 |  | 
 | #include <linux/bitops.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/device.h> | 
 | #include <linux/gpio.h> | 
 | #include <linux/gpio/consumer.h> | 
 | #include <linux/module.h> | 
 | #include <linux/mutex.h> | 
 | #include <linux/spi/spi.h> | 
 | #include <linux/sysfs.h> | 
 | #include <linux/types.h> | 
 |  | 
 | #include <linux/iio/iio.h> | 
 | #include <linux/iio/sysfs.h> | 
 |  | 
 | #define DRV_NAME "ad2s1200" | 
 |  | 
 | /* input clock on serial interface */ | 
 | #define AD2S1200_HZ	8192000 | 
 | /* clock period in nano second */ | 
 | #define AD2S1200_TSCLK	(1000000000 / AD2S1200_HZ) | 
 |  | 
 | /** | 
 |  * struct ad2s1200_state - driver instance specific data. | 
 |  * @lock:	protects both the GPIO pins and the rx buffer. | 
 |  * @sdev:	spi device. | 
 |  * @sample:	GPIO pin SAMPLE. | 
 |  * @rdvel:	GPIO pin RDVEL. | 
 |  * @rx:		buffer for spi transfers. | 
 |  */ | 
 | struct ad2s1200_state { | 
 | 	struct mutex lock; | 
 | 	struct spi_device *sdev; | 
 | 	struct gpio_desc *sample; | 
 | 	struct gpio_desc *rdvel; | 
 | 	__be16 rx ____cacheline_aligned; | 
 | }; | 
 |  | 
 | static int ad2s1200_read_raw(struct iio_dev *indio_dev, | 
 | 			     struct iio_chan_spec const *chan, | 
 | 			     int *val, | 
 | 			     int *val2, | 
 | 			     long m) | 
 | { | 
 | 	struct ad2s1200_state *st = iio_priv(indio_dev); | 
 | 	int ret; | 
 |  | 
 | 	switch (m) { | 
 | 	case IIO_CHAN_INFO_SCALE: | 
 | 		switch (chan->type) { | 
 | 		case IIO_ANGL: | 
 | 			/* 2 * Pi / (2^12 - 1) ~= 0.001534355 */ | 
 | 			*val = 0; | 
 | 			*val2 = 1534355; | 
 | 			return IIO_VAL_INT_PLUS_NANO; | 
 | 		case IIO_ANGL_VEL: | 
 | 			/* 2 * Pi ~= 6.283185 */ | 
 | 			*val = 6; | 
 | 			*val2 = 283185; | 
 | 			return IIO_VAL_INT_PLUS_MICRO; | 
 | 		default: | 
 | 			return -EINVAL; | 
 | 		} | 
 | 		break; | 
 | 	case IIO_CHAN_INFO_RAW: | 
 | 		mutex_lock(&st->lock); | 
 | 		gpiod_set_value(st->sample, 0); | 
 |  | 
 | 		/* delay (6 * AD2S1200_TSCLK + 20) nano seconds */ | 
 | 		udelay(1); | 
 | 		gpiod_set_value(st->sample, 1); | 
 | 		gpiod_set_value(st->rdvel, !!(chan->type == IIO_ANGL)); | 
 |  | 
 | 		ret = spi_read(st->sdev, &st->rx, 2); | 
 | 		if (ret < 0) { | 
 | 			mutex_unlock(&st->lock); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		switch (chan->type) { | 
 | 		case IIO_ANGL: | 
 | 			*val = be16_to_cpup(&st->rx) >> 4; | 
 | 			break; | 
 | 		case IIO_ANGL_VEL: | 
 | 			*val = sign_extend32(be16_to_cpup(&st->rx) >> 4, 11); | 
 | 			break; | 
 | 		default: | 
 | 			mutex_unlock(&st->lock); | 
 | 			return -EINVAL; | 
 | 		} | 
 |  | 
 | 		/* delay (2 * AD2S1200_TSCLK + 20) ns for sample pulse */ | 
 | 		udelay(1); | 
 | 		mutex_unlock(&st->lock); | 
 |  | 
 | 		return IIO_VAL_INT; | 
 | 	default: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	return -EINVAL; | 
 | } | 
 |  | 
 | static const struct iio_chan_spec ad2s1200_channels[] = { | 
 | 	{ | 
 | 		.type = IIO_ANGL, | 
 | 		.indexed = 1, | 
 | 		.channel = 0, | 
 | 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), | 
 | 		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), | 
 | 	}, { | 
 | 		.type = IIO_ANGL_VEL, | 
 | 		.indexed = 1, | 
 | 		.channel = 0, | 
 | 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), | 
 | 		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), | 
 | 	} | 
 | }; | 
 |  | 
 | static const struct iio_info ad2s1200_info = { | 
 | 	.read_raw = ad2s1200_read_raw, | 
 | }; | 
 |  | 
 | static int ad2s1200_probe(struct spi_device *spi) | 
 | { | 
 | 	struct ad2s1200_state *st; | 
 | 	struct iio_dev *indio_dev; | 
 | 	int ret; | 
 |  | 
 | 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st)); | 
 | 	if (!indio_dev) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	spi_set_drvdata(spi, indio_dev); | 
 | 	st = iio_priv(indio_dev); | 
 | 	mutex_init(&st->lock); | 
 | 	st->sdev = spi; | 
 |  | 
 | 	st->sample = devm_gpiod_get(&spi->dev, "adi,sample", GPIOD_OUT_LOW); | 
 | 	if (IS_ERR(st->sample)) { | 
 | 		dev_err(&spi->dev, "Failed to claim SAMPLE gpio: err=%ld\n", | 
 | 			PTR_ERR(st->sample)); | 
 | 		return PTR_ERR(st->sample); | 
 | 	} | 
 |  | 
 | 	st->rdvel = devm_gpiod_get(&spi->dev, "adi,rdvel", GPIOD_OUT_LOW); | 
 | 	if (IS_ERR(st->rdvel)) { | 
 | 		dev_err(&spi->dev, "Failed to claim RDVEL gpio: err=%ld\n", | 
 | 			PTR_ERR(st->rdvel)); | 
 | 		return PTR_ERR(st->rdvel); | 
 | 	} | 
 |  | 
 | 	indio_dev->dev.parent = &spi->dev; | 
 | 	indio_dev->info = &ad2s1200_info; | 
 | 	indio_dev->modes = INDIO_DIRECT_MODE; | 
 | 	indio_dev->channels = ad2s1200_channels; | 
 | 	indio_dev->num_channels = ARRAY_SIZE(ad2s1200_channels); | 
 | 	indio_dev->name = spi_get_device_id(spi)->name; | 
 |  | 
 | 	spi->max_speed_hz = AD2S1200_HZ; | 
 | 	spi->mode = SPI_MODE_3; | 
 | 	ret = spi_setup(spi); | 
 |  | 
 | 	if (ret < 0) { | 
 | 		dev_err(&spi->dev, "spi_setup failed!\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	return devm_iio_device_register(&spi->dev, indio_dev); | 
 | } | 
 |  | 
 | static const struct of_device_id ad2s1200_of_match[] = { | 
 | 	{ .compatible = "adi,ad2s1200", }, | 
 | 	{ .compatible = "adi,ad2s1205", }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(of, ad2s1200_of_match); | 
 |  | 
 | static const struct spi_device_id ad2s1200_id[] = { | 
 | 	{ "ad2s1200" }, | 
 | 	{ "ad2s1205" }, | 
 | 	{} | 
 | }; | 
 | MODULE_DEVICE_TABLE(spi, ad2s1200_id); | 
 |  | 
 | static struct spi_driver ad2s1200_driver = { | 
 | 	.driver = { | 
 | 		.name = DRV_NAME, | 
 | 		.of_match_table = of_match_ptr(ad2s1200_of_match), | 
 | 	}, | 
 | 	.probe = ad2s1200_probe, | 
 | 	.id_table = ad2s1200_id, | 
 | }; | 
 | module_spi_driver(ad2s1200_driver); | 
 |  | 
 | MODULE_AUTHOR("David Veenstra <davidjulianveenstra@gmail.com>"); | 
 | MODULE_AUTHOR("Graff Yang <graff.yang@gmail.com>"); | 
 | MODULE_DESCRIPTION("Analog Devices AD2S1200/1205 Resolver to Digital SPI driver"); | 
 | MODULE_LICENSE("GPL v2"); |