/*
 * AD7606 SPI ADC driver
 *
 * Copyright 2011 Analog Devices Inc.
 *
 * Licensed under the GPL-2.
 */

#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/regulator/consumer.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/module.h>

#include "../iio.h"
#include "../sysfs.h"
#include "../buffer_generic.h"

#include "ad7606.h"

int ad7606_reset(struct ad7606_state *st)
{
	if (gpio_is_valid(st->pdata->gpio_reset)) {
		gpio_set_value(st->pdata->gpio_reset, 1);
		ndelay(100); /* t_reset >= 100ns */
		gpio_set_value(st->pdata->gpio_reset, 0);
		return 0;
	}

	return -ENODEV;
}

static int ad7606_scan_direct(struct iio_dev *indio_dev, unsigned ch)
{
	struct ad7606_state *st = iio_priv(indio_dev);
	int ret;

	st->done = false;
	gpio_set_value(st->pdata->gpio_convst, 1);

	ret = wait_event_interruptible(st->wq_data_avail, st->done);
	if (ret)
		goto error_ret;

	if (gpio_is_valid(st->pdata->gpio_frstdata)) {
		ret = st->bops->read_block(st->dev, 1, st->data);
		if (ret)
			goto error_ret;
		if (!gpio_get_value(st->pdata->gpio_frstdata)) {
			/* This should never happen */
			ad7606_reset(st);
			ret = -EIO;
			goto error_ret;
		}
		ret = st->bops->read_block(st->dev,
			st->chip_info->num_channels - 1, &st->data[1]);
		if (ret)
			goto error_ret;
	} else {
		ret = st->bops->read_block(st->dev,
			st->chip_info->num_channels, st->data);
		if (ret)
			goto error_ret;
	}

	ret = st->data[ch];

error_ret:
	gpio_set_value(st->pdata->gpio_convst, 0);

	return ret;
}

static int ad7606_read_raw(struct iio_dev *indio_dev,
			   struct iio_chan_spec const *chan,
			   int *val,
			   int *val2,
			   long m)
{
	int ret;
	struct ad7606_state *st = iio_priv(indio_dev);
	unsigned int scale_uv;

	switch (m) {
	case 0:
		mutex_lock(&indio_dev->mlock);
		if (iio_buffer_enabled(indio_dev))
			ret = ad7606_scan_from_ring(indio_dev, chan->address);
		else
			ret = ad7606_scan_direct(indio_dev, chan->address);
		mutex_unlock(&indio_dev->mlock);

		if (ret < 0)
			return ret;
		*val = (short) ret;
		return IIO_VAL_INT;
	case (1 << IIO_CHAN_INFO_SCALE_SHARED):
		scale_uv = (st->range * 1000 * 2)
			>> st->chip_info->channels[0].scan_type.realbits;
		*val =  scale_uv / 1000;
		*val2 = (scale_uv % 1000) * 1000;
		return IIO_VAL_INT_PLUS_MICRO;
	}
	return -EINVAL;
}

static ssize_t ad7606_show_range(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct ad7606_state *st = iio_priv(indio_dev);

	return sprintf(buf, "%u\n", st->range);
}

static ssize_t ad7606_store_range(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct ad7606_state *st = iio_priv(indio_dev);
	unsigned long lval;

	if (strict_strtoul(buf, 10, &lval))
		return -EINVAL;
	if (!(lval == 5000 || lval == 10000)) {
		dev_err(dev, "range is not supported\n");
		return -EINVAL;
	}
	mutex_lock(&indio_dev->mlock);
	gpio_set_value(st->pdata->gpio_range, lval == 10000);
	st->range = lval;
	mutex_unlock(&indio_dev->mlock);

	return count;
}

static IIO_DEVICE_ATTR(in_voltage_range, S_IRUGO | S_IWUSR, \
		       ad7606_show_range, ad7606_store_range, 0);
static IIO_CONST_ATTR(in_voltage_range_available, "5000 10000");

static ssize_t ad7606_show_oversampling_ratio(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct ad7606_state *st = iio_priv(indio_dev);

	return sprintf(buf, "%u\n", st->oversampling);
}

static int ad7606_oversampling_get_index(unsigned val)
{
	unsigned char supported[] = {0, 2, 4, 8, 16, 32, 64};
	int i;

	for (i = 0; i < ARRAY_SIZE(supported); i++)
		if (val == supported[i])
			return i;

	return -EINVAL;
}

static ssize_t ad7606_store_oversampling_ratio(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct ad7606_state *st = iio_priv(indio_dev);
	unsigned long lval;
	int ret;

	if (strict_strtoul(buf, 10, &lval))
		return -EINVAL;

	ret = ad7606_oversampling_get_index(lval);
	if (ret < 0) {
		dev_err(dev, "oversampling %lu is not supported\n", lval);
		return ret;
	}

	mutex_lock(&indio_dev->mlock);
	gpio_set_value(st->pdata->gpio_os0, (ret >> 0) & 1);
	gpio_set_value(st->pdata->gpio_os1, (ret >> 1) & 1);
	gpio_set_value(st->pdata->gpio_os2, (ret >> 2) & 1);
	st->oversampling = lval;
	mutex_unlock(&indio_dev->mlock);

	return count;
}

static IIO_DEVICE_ATTR(oversampling_ratio, S_IRUGO | S_IWUSR,
		       ad7606_show_oversampling_ratio,
		       ad7606_store_oversampling_ratio, 0);
static IIO_CONST_ATTR(oversampling_ratio_available, "0 2 4 8 16 32 64");

static struct attribute *ad7606_attributes[] = {
	&iio_dev_attr_in_voltage_range.dev_attr.attr,
	&iio_const_attr_in_voltage_range_available.dev_attr.attr,
	&iio_dev_attr_oversampling_ratio.dev_attr.attr,
	&iio_const_attr_oversampling_ratio_available.dev_attr.attr,
	NULL,
};

static mode_t ad7606_attr_is_visible(struct kobject *kobj,
				     struct attribute *attr, int n)
{
	struct device *dev = container_of(kobj, struct device, kobj);
	struct iio_dev *indio_dev = dev_get_drvdata(dev);
	struct ad7606_state *st = iio_priv(indio_dev);

	mode_t mode = attr->mode;

	if (!(gpio_is_valid(st->pdata->gpio_os0) &&
	      gpio_is_valid(st->pdata->gpio_os1) &&
	      gpio_is_valid(st->pdata->gpio_os2)) &&
	    (attr == &iio_dev_attr_oversampling_ratio.dev_attr.attr ||
	     attr ==
	     &iio_const_attr_oversampling_ratio_available.dev_attr.attr))
		mode = 0;
	else if (!gpio_is_valid(st->pdata->gpio_range) &&
		 (attr == &iio_dev_attr_in_voltage_range.dev_attr.attr ||
		  attr ==
		  &iio_const_attr_in_voltage_range_available.dev_attr.attr))
		mode = 0;

	return mode;
}

static const struct attribute_group ad7606_attribute_group = {
	.attrs = ad7606_attributes,
	.is_visible = ad7606_attr_is_visible,
};

#define AD7606_CHANNEL(num)				\
	{						\
		.type = IIO_VOLTAGE,			\
		.indexed = 1,				\
		.channel = num,				\
		.address = num,				\
		.info_mask = (1 << IIO_CHAN_INFO_SCALE_SHARED), \
		.scan_index = num,			\
		.scan_type = IIO_ST('s', 16, 16, 0),	\
	}

static struct iio_chan_spec ad7606_8_channels[] = {
	AD7606_CHANNEL(0),
	AD7606_CHANNEL(1),
	AD7606_CHANNEL(2),
	AD7606_CHANNEL(3),
	AD7606_CHANNEL(4),
	AD7606_CHANNEL(5),
	AD7606_CHANNEL(6),
	AD7606_CHANNEL(7),
	IIO_CHAN_SOFT_TIMESTAMP(8),
};

static struct iio_chan_spec ad7606_6_channels[] = {
	AD7606_CHANNEL(0),
	AD7606_CHANNEL(1),
	AD7606_CHANNEL(2),
	AD7606_CHANNEL(3),
	AD7606_CHANNEL(4),
	AD7606_CHANNEL(5),
	IIO_CHAN_SOFT_TIMESTAMP(6),
};

static struct iio_chan_spec ad7606_4_channels[] = {
	AD7606_CHANNEL(0),
	AD7606_CHANNEL(1),
	AD7606_CHANNEL(2),
	AD7606_CHANNEL(3),
	IIO_CHAN_SOFT_TIMESTAMP(4),
};

static const struct ad7606_chip_info ad7606_chip_info_tbl[] = {
	/*
	 * More devices added in future
	 */
	[ID_AD7606_8] = {
		.name = "ad7606",
		.int_vref_mv = 2500,
		.channels = ad7606_8_channels,
		.num_channels = 8,
	},
	[ID_AD7606_6] = {
		.name = "ad7606-6",
		.int_vref_mv = 2500,
		.channels = ad7606_6_channels,
		.num_channels = 6,
	},
	[ID_AD7606_4] = {
		.name = "ad7606-4",
		.int_vref_mv = 2500,
		.channels = ad7606_4_channels,
		.num_channels = 4,
	},
};

static int ad7606_request_gpios(struct ad7606_state *st)
{
	struct gpio gpio_array[3] = {
		[0] = {
			.gpio =  st->pdata->gpio_os0,
			.flags = GPIOF_DIR_OUT | ((st->oversampling & 1) ?
				 GPIOF_INIT_HIGH : GPIOF_INIT_LOW),
			.label = "AD7606_OS0",
		},
		[1] = {
			.gpio =  st->pdata->gpio_os1,
			.flags = GPIOF_DIR_OUT | ((st->oversampling & 2) ?
				 GPIOF_INIT_HIGH : GPIOF_INIT_LOW),
			.label = "AD7606_OS1",
		},
		[2] = {
			.gpio =  st->pdata->gpio_os2,
			.flags = GPIOF_DIR_OUT | ((st->oversampling & 4) ?
				 GPIOF_INIT_HIGH : GPIOF_INIT_LOW),
			.label = "AD7606_OS2",
		},
	};
	int ret;

	if (gpio_is_valid(st->pdata->gpio_convst)) {
		ret = gpio_request_one(st->pdata->gpio_convst,
				       GPIOF_OUT_INIT_LOW,
				       "AD7606_CONVST");
		if (ret) {
			dev_err(st->dev, "failed to request GPIO CONVST\n");
			goto error_ret;
		}
	} else {
		ret = -EIO;
		goto error_ret;
	}

	if (gpio_is_valid(st->pdata->gpio_os0) &&
	    gpio_is_valid(st->pdata->gpio_os1) &&
	    gpio_is_valid(st->pdata->gpio_os2)) {
		ret = gpio_request_array(gpio_array, ARRAY_SIZE(gpio_array));
		if (ret < 0)
			goto error_free_convst;
	}

	if (gpio_is_valid(st->pdata->gpio_reset)) {
		ret = gpio_request_one(st->pdata->gpio_reset,
				       GPIOF_OUT_INIT_LOW,
				       "AD7606_RESET");
		if (ret < 0)
			goto error_free_os;
	}

	if (gpio_is_valid(st->pdata->gpio_range)) {
		ret = gpio_request_one(st->pdata->gpio_range, GPIOF_DIR_OUT |
				       ((st->range == 10000) ? GPIOF_INIT_HIGH :
					GPIOF_INIT_LOW), "AD7606_RANGE");
		if (ret < 0)
			goto error_free_reset;
	}
	if (gpio_is_valid(st->pdata->gpio_stby)) {
		ret = gpio_request_one(st->pdata->gpio_stby,
				       GPIOF_OUT_INIT_HIGH,
				       "AD7606_STBY");
		if (ret < 0)
			goto error_free_range;
	}

	if (gpio_is_valid(st->pdata->gpio_frstdata)) {
		ret = gpio_request_one(st->pdata->gpio_frstdata, GPIOF_IN,
				       "AD7606_FRSTDATA");
		if (ret < 0)
			goto error_free_stby;
	}

	return 0;

error_free_stby:
	if (gpio_is_valid(st->pdata->gpio_stby))
		gpio_free(st->pdata->gpio_stby);
error_free_range:
	if (gpio_is_valid(st->pdata->gpio_range))
		gpio_free(st->pdata->gpio_range);
error_free_reset:
	if (gpio_is_valid(st->pdata->gpio_reset))
		gpio_free(st->pdata->gpio_reset);
error_free_os:
	if (gpio_is_valid(st->pdata->gpio_os0) &&
	    gpio_is_valid(st->pdata->gpio_os1) &&
	    gpio_is_valid(st->pdata->gpio_os2))
		gpio_free_array(gpio_array, ARRAY_SIZE(gpio_array));
error_free_convst:
	gpio_free(st->pdata->gpio_convst);
error_ret:
	return ret;
}

static void ad7606_free_gpios(struct ad7606_state *st)
{
	if (gpio_is_valid(st->pdata->gpio_frstdata))
		gpio_free(st->pdata->gpio_frstdata);
	if (gpio_is_valid(st->pdata->gpio_stby))
		gpio_free(st->pdata->gpio_stby);
	if (gpio_is_valid(st->pdata->gpio_range))
		gpio_free(st->pdata->gpio_range);
	if (gpio_is_valid(st->pdata->gpio_reset))
		gpio_free(st->pdata->gpio_reset);
	if (gpio_is_valid(st->pdata->gpio_os0) &&
	    gpio_is_valid(st->pdata->gpio_os1) &&
	    gpio_is_valid(st->pdata->gpio_os2)) {
		gpio_free(st->pdata->gpio_os2);
		gpio_free(st->pdata->gpio_os1);
		gpio_free(st->pdata->gpio_os0);
	}
	gpio_free(st->pdata->gpio_convst);
}

/**
 *  Interrupt handler
 */
static irqreturn_t ad7606_interrupt(int irq, void *dev_id)
{
	struct iio_dev *indio_dev = dev_id;
	struct ad7606_state *st = iio_priv(indio_dev);

	if (iio_buffer_enabled(indio_dev)) {
		if (!work_pending(&st->poll_work))
			schedule_work(&st->poll_work);
	} else {
		st->done = true;
		wake_up_interruptible(&st->wq_data_avail);
	}

	return IRQ_HANDLED;
};

static const struct iio_info ad7606_info = {
	.driver_module = THIS_MODULE,
	.read_raw = &ad7606_read_raw,
	.attrs = &ad7606_attribute_group,
};

struct iio_dev *ad7606_probe(struct device *dev, int irq,
			      void __iomem *base_address,
			      unsigned id,
			      const struct ad7606_bus_ops *bops)
{
	struct ad7606_platform_data *pdata = dev->platform_data;
	struct ad7606_state *st;
	int ret;
	struct iio_dev *indio_dev = iio_allocate_device(sizeof(*st));

	if (indio_dev == NULL) {
		ret = -ENOMEM;
		goto error_ret;
	}

	st = iio_priv(indio_dev);

	st->dev = dev;
	st->bops = bops;
	st->base_address = base_address;
	st->range = pdata->default_range == 10000 ? 10000 : 5000;

	ret = ad7606_oversampling_get_index(pdata->default_os);
	if (ret < 0) {
		dev_warn(dev, "oversampling %d is not supported\n",
			 pdata->default_os);
		st->oversampling = 0;
	} else {
		st->oversampling = pdata->default_os;
	}

	st->reg = regulator_get(dev, "vcc");
	if (!IS_ERR(st->reg)) {
		ret = regulator_enable(st->reg);
		if (ret)
			goto error_put_reg;
	}

	st->pdata = pdata;
	st->chip_info = &ad7606_chip_info_tbl[id];

	indio_dev->dev.parent = dev;
	indio_dev->info = &ad7606_info;
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->name = st->chip_info->name;
	indio_dev->channels = st->chip_info->channels;
	indio_dev->num_channels = st->chip_info->num_channels;

	init_waitqueue_head(&st->wq_data_avail);

	ret = ad7606_request_gpios(st);
	if (ret)
		goto error_disable_reg;

	ret = ad7606_reset(st);
	if (ret)
		dev_warn(st->dev, "failed to RESET: no RESET GPIO specified\n");

	ret = request_irq(irq, ad7606_interrupt,
		IRQF_TRIGGER_FALLING, st->chip_info->name, indio_dev);
	if (ret)
		goto error_free_gpios;

	ret = ad7606_register_ring_funcs_and_init(indio_dev);
	if (ret)
		goto error_free_irq;

	ret = iio_buffer_register(indio_dev,
				  indio_dev->channels,
				  indio_dev->num_channels);
	if (ret)
		goto error_cleanup_ring;
	ret = iio_device_register(indio_dev);
	if (ret)
		goto error_unregister_ring;

	return indio_dev;
error_unregister_ring:
	iio_buffer_unregister(indio_dev);

error_cleanup_ring:
	ad7606_ring_cleanup(indio_dev);

error_free_irq:
	free_irq(irq, indio_dev);

error_free_gpios:
	ad7606_free_gpios(st);

error_disable_reg:
	if (!IS_ERR(st->reg))
		regulator_disable(st->reg);
error_put_reg:
	if (!IS_ERR(st->reg))
		regulator_put(st->reg);
	iio_free_device(indio_dev);
error_ret:
	return ERR_PTR(ret);
}

int ad7606_remove(struct iio_dev *indio_dev, int irq)
{
	struct ad7606_state *st = iio_priv(indio_dev);

	iio_device_unregister(indio_dev);
	iio_buffer_unregister(indio_dev);
	ad7606_ring_cleanup(indio_dev);

	free_irq(irq, indio_dev);
	if (!IS_ERR(st->reg)) {
		regulator_disable(st->reg);
		regulator_put(st->reg);
	}

	ad7606_free_gpios(st);
	iio_free_device(indio_dev);

	return 0;
}

void ad7606_suspend(struct iio_dev *indio_dev)
{
	struct ad7606_state *st = iio_priv(indio_dev);

	if (gpio_is_valid(st->pdata->gpio_stby)) {
		if (gpio_is_valid(st->pdata->gpio_range))
			gpio_set_value(st->pdata->gpio_range, 1);
		gpio_set_value(st->pdata->gpio_stby, 0);
	}
}

void ad7606_resume(struct iio_dev *indio_dev)
{
	struct ad7606_state *st = iio_priv(indio_dev);

	if (gpio_is_valid(st->pdata->gpio_stby)) {
		if (gpio_is_valid(st->pdata->gpio_range))
			gpio_set_value(st->pdata->gpio_range,
					st->range == 10000);

		gpio_set_value(st->pdata->gpio_stby, 1);
		ad7606_reset(st);
	}
}

MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("Analog Devices AD7606 ADC");
MODULE_LICENSE("GPL v2");
