Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Maxim Kiselev 1140 100.00% 1 100.00%
Total 1140 1


// SPDX-License-Identifier: GPL-2.0
/*
 * GPADC driver for sunxi platforms (D1, T113-S3 and R329)
 * Copyright (c) 2023 Maksim Kiselev <bigunclemax@gmail.com>
 */

#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/reset.h>

#include <linux/iio/iio.h>

#define SUN20I_GPADC_DRIVER_NAME	"sun20i-gpadc"

/* Register map definition */
#define SUN20I_GPADC_SR			0x00
#define SUN20I_GPADC_CTRL		0x04
#define SUN20I_GPADC_CS_EN		0x08
#define SUN20I_GPADC_FIFO_INTC		0x0c
#define SUN20I_GPADC_FIFO_INTS		0x10
#define SUN20I_GPADC_FIFO_DATA		0X14
#define SUN20I_GPADC_CB_DATA		0X18
#define SUN20I_GPADC_DATAL_INTC		0x20
#define SUN20I_GPADC_DATAH_INTC		0x24
#define SUN20I_GPADC_DATA_INTC		0x28
#define SUN20I_GPADC_DATAL_INTS		0x30
#define SUN20I_GPADC_DATAH_INTS		0x34
#define SUN20I_GPADC_DATA_INTS		0x38
#define SUN20I_GPADC_CH_CMP_DATA(x)	(0x40 + (x) * 4)
#define SUN20I_GPADC_CH_DATA(x)		(0x80 + (x) * 4)

#define SUN20I_GPADC_CTRL_ADC_AUTOCALI_EN_MASK		BIT(23)
#define SUN20I_GPADC_CTRL_WORK_MODE_MASK		GENMASK(19, 18)
#define SUN20I_GPADC_CTRL_ADC_EN_MASK			BIT(16)
#define SUN20I_GPADC_CS_EN_ADC_CH(x)			BIT(x)
#define SUN20I_GPADC_DATA_INTC_CH_DATA_IRQ_EN(x)	BIT(x)

#define SUN20I_GPADC_WORK_MODE_SINGLE			0

struct sun20i_gpadc_iio {
	void __iomem		*regs;
	struct completion	completion;
	int			last_channel;
	/*
	 * Lock to protect the device state during a potential concurrent
	 * read access from userspace. Reading a raw value requires a sequence
	 * of register writes, then a wait for a completion callback,
	 * and finally a register read, during which userspace could issue
	 * another read request. This lock protects a read access from
	 * ocurring before another one has finished.
	 */
	struct mutex		lock;
};

static int sun20i_gpadc_adc_read(struct sun20i_gpadc_iio *info,
				 struct iio_chan_spec const *chan, int *val)
{
	u32 ctrl;
	int ret = IIO_VAL_INT;

	mutex_lock(&info->lock);

	reinit_completion(&info->completion);

	if (info->last_channel != chan->channel) {
		info->last_channel = chan->channel;

		/* enable the analog input channel */
		writel(SUN20I_GPADC_CS_EN_ADC_CH(chan->channel),
		       info->regs + SUN20I_GPADC_CS_EN);

		/* enable the data irq for input channel */
		writel(SUN20I_GPADC_DATA_INTC_CH_DATA_IRQ_EN(chan->channel),
		       info->regs + SUN20I_GPADC_DATA_INTC);
	}

	/* enable the ADC function */
	ctrl = readl(info->regs + SUN20I_GPADC_CTRL);
	ctrl |= FIELD_PREP(SUN20I_GPADC_CTRL_ADC_EN_MASK, 1);
	writel(ctrl, info->regs + SUN20I_GPADC_CTRL);

	/*
	 * According to the datasheet maximum acquire time(TACQ) can be
	 * (65535+1)/24Mhz and conversion time(CONV_TIME) is always constant
	 * and equal to 14/24Mhz, so (TACQ+CONV_TIME) <= 2.73125ms.
	 * A 10ms delay should be enough to make sure an interrupt occurs in
	 * normal conditions. If it doesn't occur, then there is a timeout.
	 */
	if (!wait_for_completion_timeout(&info->completion, msecs_to_jiffies(10))) {
		ret = -ETIMEDOUT;
		goto err_unlock;
	}

	/* read the ADC data */
	*val = readl(info->regs + SUN20I_GPADC_CH_DATA(chan->channel));

err_unlock:
	mutex_unlock(&info->lock);

	return ret;
}

static int sun20i_gpadc_read_raw(struct iio_dev *indio_dev,
				 struct iio_chan_spec const *chan, int *val,
				 int *val2, long mask)
{
	struct sun20i_gpadc_iio *info = iio_priv(indio_dev);

	switch (mask) {
	case IIO_CHAN_INFO_RAW:
		return sun20i_gpadc_adc_read(info, chan, val);
	case IIO_CHAN_INFO_SCALE:
		/* value in mv = 1800mV / 4096 raw */
		*val = 1800;
		*val2 = 12;
		return IIO_VAL_FRACTIONAL_LOG2;
	default:
		return -EINVAL;
	}
}

static irqreturn_t sun20i_gpadc_irq_handler(int irq, void *data)
{
	struct sun20i_gpadc_iio *info = data;

	/* clear data interrupt status register */
	writel(GENMASK(31, 0), info->regs + SUN20I_GPADC_DATA_INTS);

	complete(&info->completion);

	return IRQ_HANDLED;
}

static const struct iio_info sun20i_gpadc_iio_info = {
	.read_raw = sun20i_gpadc_read_raw,
};

static void sun20i_gpadc_reset_assert(void *data)
{
	struct reset_control *rst = data;

	reset_control_assert(rst);
}

static int sun20i_gpadc_alloc_channels(struct iio_dev *indio_dev,
				       struct device *dev)
{
	unsigned int channel;
	int num_channels, i, ret;
	struct iio_chan_spec *channels;
	struct fwnode_handle *node;

	num_channels = device_get_child_node_count(dev);
	if (num_channels == 0)
		return dev_err_probe(dev, -ENODEV, "no channel children\n");

	channels = devm_kcalloc(dev, num_channels, sizeof(*channels),
				GFP_KERNEL);
	if (!channels)
		return -ENOMEM;

	i = 0;
	device_for_each_child_node(dev, node) {
		ret = fwnode_property_read_u32(node, "reg", &channel);
		if (ret) {
			fwnode_handle_put(node);
			return dev_err_probe(dev, ret, "invalid channel number\n");
		}

		channels[i].type = IIO_VOLTAGE;
		channels[i].indexed = 1;
		channels[i].channel = channel;
		channels[i].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
		channels[i].info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE);

		i++;
	}

	indio_dev->channels = channels;
	indio_dev->num_channels = num_channels;

	return 0;
}

static int sun20i_gpadc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct iio_dev *indio_dev;
	struct sun20i_gpadc_iio *info;
	struct reset_control *rst;
	struct clk *clk;
	int irq;
	int ret;

	indio_dev = devm_iio_device_alloc(dev, sizeof(*info));
	if (!indio_dev)
		return -ENOMEM;

	info = iio_priv(indio_dev);
	info->last_channel = -1;

	mutex_init(&info->lock);
	init_completion(&info->completion);

	ret = sun20i_gpadc_alloc_channels(indio_dev, dev);
	if (ret)
		return ret;

	indio_dev->info = &sun20i_gpadc_iio_info;
	indio_dev->name = SUN20I_GPADC_DRIVER_NAME;

	info->regs = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(info->regs))
		return PTR_ERR(info->regs);

	clk = devm_clk_get_enabled(dev, NULL);
	if (IS_ERR(clk))
		return dev_err_probe(dev, PTR_ERR(clk), "failed to enable bus clock\n");

	rst = devm_reset_control_get_exclusive(dev, NULL);
	if (IS_ERR(rst))
		return dev_err_probe(dev, PTR_ERR(rst), "failed to get reset control\n");

	ret = reset_control_deassert(rst);
	if (ret)
		return dev_err_probe(dev, ret, "failed to deassert reset\n");

	ret = devm_add_action_or_reset(dev, sun20i_gpadc_reset_assert, rst);
	if (ret)
		return ret;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0)
		return irq;

	ret = devm_request_irq(dev, irq, sun20i_gpadc_irq_handler, 0,
			       dev_name(dev), info);
	if (ret)
		return dev_err_probe(dev, ret, "failed requesting irq %d\n", irq);

	writel(FIELD_PREP(SUN20I_GPADC_CTRL_ADC_AUTOCALI_EN_MASK, 1) |
	       FIELD_PREP(SUN20I_GPADC_CTRL_WORK_MODE_MASK, SUN20I_GPADC_WORK_MODE_SINGLE),
	       info->regs + SUN20I_GPADC_CTRL);

	ret = devm_iio_device_register(dev, indio_dev);
	if (ret)
		return dev_err_probe(dev, ret, "could not register the device\n");

	return 0;
}

static const struct of_device_id sun20i_gpadc_of_id[] = {
	{ .compatible = "allwinner,sun20i-d1-gpadc" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sun20i_gpadc_of_id);

static struct platform_driver sun20i_gpadc_driver = {
	.driver = {
		.name = SUN20I_GPADC_DRIVER_NAME,
		.of_match_table = sun20i_gpadc_of_id,
	},
	.probe = sun20i_gpadc_probe,
};
module_platform_driver(sun20i_gpadc_driver);

MODULE_DESCRIPTION("ADC driver for sunxi platforms");
MODULE_AUTHOR("Maksim Kiselev <bigunclemax@gmail.com>");
MODULE_LICENSE("GPL");