summaryrefslogtreecommitdiff
path: root/drivers/iio/adc/sophgo-cv1800b-adc.c
blob: 0951deb7b111233ccf2b79fefbd4a489864fc86c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Sophgo CV1800B SARADC Driver
 *
 *  Copyright (C) Bootlin 2024
 *  Author: Thomas Bonnefille <thomas.bonnefille@bootlin.com>
 */

#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/iopoll.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/types.h>

#include <linux/iio/iio.h>

#define CV1800B_ADC_CTRL_REG				0x04
#define	  CV1800B_ADC_EN				BIT(0)
#define	  CV1800B_ADC_SEL(x)				BIT((x) + 5)
#define CV1800B_ADC_STATUS_REG				0x08
#define	  CV1800B_ADC_BUSY				BIT(0)
#define CV1800B_ADC_CYC_SET_REG				0x0C
#define   CV1800B_MASK_STARTUP_CYCLE			GENMASK(4, 0)
#define   CV1800B_MASK_SAMPLE_WINDOW			GENMASK(11, 8)
#define   CV1800B_MASK_CLKDIV				GENMASK(15, 12)
#define   CV1800B_MASK_COMPARE_CYCLE			GENMASK(19, 16)
#define CV1800B_ADC_CH_RESULT_REG(x)			(0x14 + 4 * (x))
#define	  CV1800B_ADC_CH_RESULT				GENMASK(11, 0)
#define	  CV1800B_ADC_CH_VALID				BIT(15)
#define CV1800B_ADC_INTR_EN_REG				0x20
#define CV1800B_ADC_INTR_CLR_REG			0x24
#define	  CV1800B_ADC_INTR_CLR_BIT			BIT(0)
#define CV1800B_ADC_INTR_STA_REG			0x28
#define	  CV1800B_ADC_INTR_STA_BIT			BIT(0)
#define CV1800B_READ_TIMEOUT_MS				1000
#define CV1800B_READ_TIMEOUT_US				(CV1800B_READ_TIMEOUT_MS * 1000)

#define CV1800B_ADC_CHANNEL(index)					\
	{								\
		.type = IIO_VOLTAGE,					\
		.indexed = 1,						\
		.channel = index,					\
		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),		\
		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),	\
		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
		.scan_index = index,					\
	}

struct cv1800b_adc {
	struct completion completion;
	void __iomem *regs;
	struct mutex lock; /* ADC Control and Result register */
	struct clk *clk;
	int irq;
};

static const struct iio_chan_spec sophgo_channels[] = {
	CV1800B_ADC_CHANNEL(0),
	CV1800B_ADC_CHANNEL(1),
	CV1800B_ADC_CHANNEL(2),
};

static void cv1800b_adc_start_measurement(struct cv1800b_adc *saradc,
					  int channel)
{
	writel(0, saradc->regs + CV1800B_ADC_CTRL_REG);
	writel(CV1800B_ADC_SEL(channel) | CV1800B_ADC_EN,
	       saradc->regs + CV1800B_ADC_CTRL_REG);
}

static int cv1800b_adc_wait(struct cv1800b_adc *saradc)
{
	if (saradc->irq < 0) {
		u32 reg;

		return readl_poll_timeout(saradc->regs + CV1800B_ADC_STATUS_REG,
					  reg, !(reg & CV1800B_ADC_BUSY),
					  500, CV1800B_READ_TIMEOUT_US);
	}

	return wait_for_completion_timeout(&saradc->completion,
					   msecs_to_jiffies(CV1800B_READ_TIMEOUT_MS)) > 0 ?
					   0 : -ETIMEDOUT;
}

static int cv1800b_adc_read_raw(struct iio_dev *indio_dev,
				struct iio_chan_spec const *chan,
				int *val, int *val2, long mask)
{
	struct cv1800b_adc *saradc = iio_priv(indio_dev);

	switch (mask) {
	case IIO_CHAN_INFO_RAW: {
		u32 sample;

		scoped_guard(mutex, &saradc->lock) {
			int ret;

			cv1800b_adc_start_measurement(saradc, chan->scan_index);
			ret = cv1800b_adc_wait(saradc);
			if (ret < 0)
				return ret;

			sample = readl(saradc->regs + CV1800B_ADC_CH_RESULT_REG(chan->scan_index));
		}
		if (!(sample & CV1800B_ADC_CH_VALID))
			return -ENODATA;

		*val = sample & CV1800B_ADC_CH_RESULT;
		return IIO_VAL_INT;
	}
	case IIO_CHAN_INFO_SCALE:
		*val = 3300;
		*val2 = 12;
		return IIO_VAL_FRACTIONAL_LOG2;
	case IIO_CHAN_INFO_SAMP_FREQ: {
		u32 status_reg = readl(saradc->regs + CV1800B_ADC_CYC_SET_REG);
		unsigned int clk_div = (1 + FIELD_GET(CV1800B_MASK_CLKDIV, status_reg));
		unsigned int freq = clk_get_rate(saradc->clk) / clk_div;
		unsigned int nb_startup_cycle = 1 + FIELD_GET(CV1800B_MASK_STARTUP_CYCLE, status_reg);
		unsigned int nb_sample_cycle = 1 + FIELD_GET(CV1800B_MASK_SAMPLE_WINDOW, status_reg);
		unsigned int nb_compare_cycle = 1 + FIELD_GET(CV1800B_MASK_COMPARE_CYCLE, status_reg);

		*val = freq / (nb_startup_cycle + nb_sample_cycle + nb_compare_cycle);
		return IIO_VAL_INT;
	}
	default:
		return -EINVAL;
	}
}

static irqreturn_t cv1800b_adc_interrupt_handler(int irq, void *private)
{
	struct cv1800b_adc *saradc = private;
	u32 reg = readl(saradc->regs + CV1800B_ADC_INTR_STA_REG);

	if (!(FIELD_GET(CV1800B_ADC_INTR_STA_BIT, reg)))
		return IRQ_NONE;

	writel(CV1800B_ADC_INTR_CLR_BIT, saradc->regs + CV1800B_ADC_INTR_CLR_REG);
	complete(&saradc->completion);

	return IRQ_HANDLED;
}

static const struct iio_info cv1800b_adc_info = {
	.read_raw = &cv1800b_adc_read_raw,
};

static int cv1800b_adc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct cv1800b_adc *saradc;
	struct iio_dev *indio_dev;
	int ret;

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

	saradc = iio_priv(indio_dev);
	indio_dev->name = "sophgo-cv1800b-adc";
	indio_dev->modes = INDIO_DIRECT_MODE;
	indio_dev->info = &cv1800b_adc_info;
	indio_dev->num_channels = ARRAY_SIZE(sophgo_channels);
	indio_dev->channels = sophgo_channels;

	saradc->clk = devm_clk_get_enabled(dev, NULL);
	if (IS_ERR(saradc->clk))
		return PTR_ERR(saradc->clk);

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

	saradc->irq = platform_get_irq_optional(pdev, 0);
	if (saradc->irq > 0) {
		init_completion(&saradc->completion);
		ret = devm_request_irq(dev, saradc->irq,
				       cv1800b_adc_interrupt_handler, 0,
				       dev_name(dev), saradc);
		if (ret)
			return ret;

		writel(1, saradc->regs + CV1800B_ADC_INTR_EN_REG);
	}

	ret = devm_mutex_init(dev, &saradc->lock);
	if (ret)
		return ret;

	writel(FIELD_PREP(CV1800B_MASK_STARTUP_CYCLE, 15) |
	       FIELD_PREP(CV1800B_MASK_SAMPLE_WINDOW, 15) |
	       FIELD_PREP(CV1800B_MASK_CLKDIV, 1) |
	       FIELD_PREP(CV1800B_MASK_COMPARE_CYCLE, 15),
	       saradc->regs + CV1800B_ADC_CYC_SET_REG);

	return devm_iio_device_register(dev, indio_dev);
}

static const struct of_device_id cv1800b_adc_match[] = {
	{ .compatible = "sophgo,cv1800b-saradc", },
	{ }
};
MODULE_DEVICE_TABLE(of, cv1800b_adc_match);

static struct platform_driver cv1800b_adc_driver = {
	.driver	= {
		.name		= "sophgo-cv1800b-saradc",
		.of_match_table	= cv1800b_adc_match,
	},
	.probe = cv1800b_adc_probe,
};
module_platform_driver(cv1800b_adc_driver);

MODULE_AUTHOR("Thomas Bonnefille <thomas.bonnefille@bootlin.com>");
MODULE_DESCRIPTION("Sophgo CV1800B SARADC driver");
MODULE_LICENSE("GPL");