09e3bdfe49
This is a frequent minor comment in reviews, so start cleaning up existing drivers in the hope we get fewer cases of cut and paste. There are not kernel wide rules for these, but for IIO the style that I prefer (and hence most common) is: - Space after { and before } - No comma after terminator { } This may cause merge conflicts but they should be trivial to resolve hence I have not broken this into per driver patches. Link: https://patch.msgid.link/20240818180912.719399-1-jic23@kernel.org Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
237 lines
5.7 KiB
C
237 lines
5.7 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* lpc32xx_adc.c - Support for ADC in LPC32XX
|
|
*
|
|
* 3-channel, 10-bit ADC
|
|
*
|
|
* Copyright (C) 2011, 2012 Roland Stigge <stigge@antcom.de>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/err.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
/*
|
|
* LPC32XX registers definitions
|
|
*/
|
|
#define LPC32XXAD_SELECT(x) ((x) + 0x04)
|
|
#define LPC32XXAD_CTRL(x) ((x) + 0x08)
|
|
#define LPC32XXAD_VALUE(x) ((x) + 0x48)
|
|
|
|
/* Bit definitions for LPC32XXAD_SELECT: */
|
|
/* constant, always write this value! */
|
|
#define LPC32XXAD_REFm 0x00000200
|
|
/* constant, always write this value! */
|
|
#define LPC32XXAD_REFp 0x00000080
|
|
/* multiple of this is the channel number: 0, 1, 2 */
|
|
#define LPC32XXAD_IN 0x00000010
|
|
/* constant, always write this value! */
|
|
#define LPC32XXAD_INTERNAL 0x00000004
|
|
|
|
/* Bit definitions for LPC32XXAD_CTRL: */
|
|
#define LPC32XXAD_STROBE 0x00000002
|
|
#define LPC32XXAD_PDN_CTRL 0x00000004
|
|
|
|
/* Bit definitions for LPC32XXAD_VALUE: */
|
|
#define LPC32XXAD_VALUE_MASK 0x000003FF
|
|
|
|
#define LPC32XXAD_NAME "lpc32xx-adc"
|
|
|
|
struct lpc32xx_adc_state {
|
|
void __iomem *adc_base;
|
|
struct clk *clk;
|
|
struct completion completion;
|
|
struct regulator *vref;
|
|
/* lock to protect against multiple access to the device */
|
|
struct mutex lock;
|
|
|
|
u32 value;
|
|
};
|
|
|
|
static int lpc32xx_read_raw(struct iio_dev *indio_dev,
|
|
struct iio_chan_spec const *chan,
|
|
int *val,
|
|
int *val2,
|
|
long mask)
|
|
{
|
|
struct lpc32xx_adc_state *st = iio_priv(indio_dev);
|
|
int ret;
|
|
|
|
switch (mask) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
mutex_lock(&st->lock);
|
|
ret = clk_prepare_enable(st->clk);
|
|
if (ret) {
|
|
mutex_unlock(&st->lock);
|
|
return ret;
|
|
}
|
|
/* Measurement setup */
|
|
__raw_writel(LPC32XXAD_INTERNAL | (chan->address) |
|
|
LPC32XXAD_REFp | LPC32XXAD_REFm,
|
|
LPC32XXAD_SELECT(st->adc_base));
|
|
/* Trigger conversion */
|
|
__raw_writel(LPC32XXAD_PDN_CTRL | LPC32XXAD_STROBE,
|
|
LPC32XXAD_CTRL(st->adc_base));
|
|
wait_for_completion(&st->completion); /* set by ISR */
|
|
clk_disable_unprepare(st->clk);
|
|
*val = st->value;
|
|
mutex_unlock(&st->lock);
|
|
|
|
return IIO_VAL_INT;
|
|
|
|
case IIO_CHAN_INFO_SCALE:
|
|
*val = regulator_get_voltage(st->vref) / 1000;
|
|
*val2 = 10;
|
|
|
|
return IIO_VAL_FRACTIONAL_LOG2;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static const struct iio_info lpc32xx_adc_iio_info = {
|
|
.read_raw = &lpc32xx_read_raw,
|
|
};
|
|
|
|
#define LPC32XX_ADC_CHANNEL_BASE(_index) \
|
|
.type = IIO_VOLTAGE, \
|
|
.indexed = 1, \
|
|
.channel = _index, \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
|
|
.address = LPC32XXAD_IN * _index, \
|
|
.scan_index = _index,
|
|
|
|
#define LPC32XX_ADC_CHANNEL(_index) { \
|
|
LPC32XX_ADC_CHANNEL_BASE(_index) \
|
|
}
|
|
|
|
#define LPC32XX_ADC_SCALE_CHANNEL(_index) { \
|
|
LPC32XX_ADC_CHANNEL_BASE(_index) \
|
|
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) \
|
|
}
|
|
|
|
static const struct iio_chan_spec lpc32xx_adc_iio_channels[] = {
|
|
LPC32XX_ADC_CHANNEL(0),
|
|
LPC32XX_ADC_CHANNEL(1),
|
|
LPC32XX_ADC_CHANNEL(2),
|
|
};
|
|
|
|
static const struct iio_chan_spec lpc32xx_adc_iio_scale_channels[] = {
|
|
LPC32XX_ADC_SCALE_CHANNEL(0),
|
|
LPC32XX_ADC_SCALE_CHANNEL(1),
|
|
LPC32XX_ADC_SCALE_CHANNEL(2),
|
|
};
|
|
|
|
static irqreturn_t lpc32xx_adc_isr(int irq, void *dev_id)
|
|
{
|
|
struct lpc32xx_adc_state *st = dev_id;
|
|
|
|
/* Read value and clear irq */
|
|
st->value = __raw_readl(LPC32XXAD_VALUE(st->adc_base)) &
|
|
LPC32XXAD_VALUE_MASK;
|
|
complete(&st->completion);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int lpc32xx_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct lpc32xx_adc_state *st = NULL;
|
|
struct resource *res;
|
|
int retval = -ENODEV;
|
|
struct iio_dev *iodev = NULL;
|
|
int irq;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
dev_err(&pdev->dev, "failed to get platform I/O memory\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*st));
|
|
if (!iodev)
|
|
return -ENOMEM;
|
|
|
|
st = iio_priv(iodev);
|
|
|
|
st->adc_base = devm_ioremap(&pdev->dev, res->start,
|
|
resource_size(res));
|
|
if (!st->adc_base) {
|
|
dev_err(&pdev->dev, "failed mapping memory\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
st->clk = devm_clk_get(&pdev->dev, NULL);
|
|
if (IS_ERR(st->clk)) {
|
|
dev_err(&pdev->dev, "failed getting clock\n");
|
|
return PTR_ERR(st->clk);
|
|
}
|
|
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
retval = devm_request_irq(&pdev->dev, irq, lpc32xx_adc_isr, 0,
|
|
LPC32XXAD_NAME, st);
|
|
if (retval < 0) {
|
|
dev_err(&pdev->dev, "failed requesting interrupt\n");
|
|
return retval;
|
|
}
|
|
|
|
st->vref = devm_regulator_get(&pdev->dev, "vref");
|
|
if (IS_ERR(st->vref)) {
|
|
iodev->channels = lpc32xx_adc_iio_channels;
|
|
dev_info(&pdev->dev,
|
|
"Missing vref regulator: No scaling available\n");
|
|
} else {
|
|
iodev->channels = lpc32xx_adc_iio_scale_channels;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, iodev);
|
|
|
|
init_completion(&st->completion);
|
|
|
|
iodev->name = LPC32XXAD_NAME;
|
|
iodev->info = &lpc32xx_adc_iio_info;
|
|
iodev->modes = INDIO_DIRECT_MODE;
|
|
iodev->num_channels = ARRAY_SIZE(lpc32xx_adc_iio_channels);
|
|
|
|
mutex_init(&st->lock);
|
|
|
|
retval = devm_iio_device_register(&pdev->dev, iodev);
|
|
if (retval)
|
|
return retval;
|
|
|
|
dev_info(&pdev->dev, "LPC32XX ADC driver loaded, IRQ %d\n", irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id lpc32xx_adc_match[] = {
|
|
{ .compatible = "nxp,lpc3220-adc" },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, lpc32xx_adc_match);
|
|
|
|
static struct platform_driver lpc32xx_adc_driver = {
|
|
.probe = lpc32xx_adc_probe,
|
|
.driver = {
|
|
.name = LPC32XXAD_NAME,
|
|
.of_match_table = lpc32xx_adc_match,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(lpc32xx_adc_driver);
|
|
|
|
MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>");
|
|
MODULE_DESCRIPTION("LPC32XX ADC driver");
|
|
MODULE_LICENSE("GPL");
|