mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 10:25:26 +07:00
c942fddf87
Based on 3 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version [author] [graeme] [gregory] [gg]@[slimlogic] [co] [uk] [author] [kishon] [vijay] [abraham] [i] [kishon]@[ti] [com] [based] [on] [twl6030]_[usb] [c] [author] [hema] [hk] [hemahk]@[ti] [com] this program is distributed in the hope that it will be useful but without any warranty without even the implied warranty of merchantability or fitness for a particular purpose see the gnu general public license for more details extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 1105 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Reviewed-by: Richard Fontana <rfontana@redhat.com> Reviewed-by: Kate Stewart <kstewart@linuxfoundation.org> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070033.202006027@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
838 lines
23 KiB
C
838 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Freescale MXS LRADC ADC driver
|
|
*
|
|
* Copyright (c) 2012 DENX Software Engineering, GmbH.
|
|
* Copyright (c) 2017 Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
|
|
*
|
|
* Authors:
|
|
* Marek Vasut <marex@denx.de>
|
|
* Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
|
|
*/
|
|
|
|
#include <linux/completion.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/mfd/core.h>
|
|
#include <linux/mfd/mxs-lradc.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/sysfs.h>
|
|
|
|
#include <linux/iio/buffer.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/iio/trigger.h>
|
|
#include <linux/iio/trigger_consumer.h>
|
|
#include <linux/iio/triggered_buffer.h>
|
|
#include <linux/iio/sysfs.h>
|
|
|
|
/*
|
|
* Make this runtime configurable if necessary. Currently, if the buffered mode
|
|
* is enabled, the LRADC takes LRADC_DELAY_TIMER_LOOP samples of data before
|
|
* triggering IRQ. The sampling happens every (LRADC_DELAY_TIMER_PER / 2000)
|
|
* seconds. The result is that the samples arrive every 500mS.
|
|
*/
|
|
#define LRADC_DELAY_TIMER_PER 200
|
|
#define LRADC_DELAY_TIMER_LOOP 5
|
|
|
|
#define VREF_MV_BASE 1850
|
|
|
|
static const char *mx23_lradc_adc_irq_names[] = {
|
|
"mxs-lradc-channel0",
|
|
"mxs-lradc-channel1",
|
|
"mxs-lradc-channel2",
|
|
"mxs-lradc-channel3",
|
|
"mxs-lradc-channel4",
|
|
"mxs-lradc-channel5",
|
|
};
|
|
|
|
static const char *mx28_lradc_adc_irq_names[] = {
|
|
"mxs-lradc-thresh0",
|
|
"mxs-lradc-thresh1",
|
|
"mxs-lradc-channel0",
|
|
"mxs-lradc-channel1",
|
|
"mxs-lradc-channel2",
|
|
"mxs-lradc-channel3",
|
|
"mxs-lradc-channel4",
|
|
"mxs-lradc-channel5",
|
|
"mxs-lradc-button0",
|
|
"mxs-lradc-button1",
|
|
};
|
|
|
|
static const u32 mxs_lradc_adc_vref_mv[][LRADC_MAX_TOTAL_CHANS] = {
|
|
[IMX23_LRADC] = {
|
|
VREF_MV_BASE, /* CH0 */
|
|
VREF_MV_BASE, /* CH1 */
|
|
VREF_MV_BASE, /* CH2 */
|
|
VREF_MV_BASE, /* CH3 */
|
|
VREF_MV_BASE, /* CH4 */
|
|
VREF_MV_BASE, /* CH5 */
|
|
VREF_MV_BASE * 2, /* CH6 VDDIO */
|
|
VREF_MV_BASE * 4, /* CH7 VBATT */
|
|
VREF_MV_BASE, /* CH8 Temp sense 0 */
|
|
VREF_MV_BASE, /* CH9 Temp sense 1 */
|
|
VREF_MV_BASE, /* CH10 */
|
|
VREF_MV_BASE, /* CH11 */
|
|
VREF_MV_BASE, /* CH12 USB_DP */
|
|
VREF_MV_BASE, /* CH13 USB_DN */
|
|
VREF_MV_BASE, /* CH14 VBG */
|
|
VREF_MV_BASE * 4, /* CH15 VDD5V */
|
|
},
|
|
[IMX28_LRADC] = {
|
|
VREF_MV_BASE, /* CH0 */
|
|
VREF_MV_BASE, /* CH1 */
|
|
VREF_MV_BASE, /* CH2 */
|
|
VREF_MV_BASE, /* CH3 */
|
|
VREF_MV_BASE, /* CH4 */
|
|
VREF_MV_BASE, /* CH5 */
|
|
VREF_MV_BASE, /* CH6 */
|
|
VREF_MV_BASE * 4, /* CH7 VBATT */
|
|
VREF_MV_BASE, /* CH8 Temp sense 0 */
|
|
VREF_MV_BASE, /* CH9 Temp sense 1 */
|
|
VREF_MV_BASE * 2, /* CH10 VDDIO */
|
|
VREF_MV_BASE, /* CH11 VTH */
|
|
VREF_MV_BASE * 2, /* CH12 VDDA */
|
|
VREF_MV_BASE, /* CH13 VDDD */
|
|
VREF_MV_BASE, /* CH14 VBG */
|
|
VREF_MV_BASE * 4, /* CH15 VDD5V */
|
|
},
|
|
};
|
|
|
|
enum mxs_lradc_divbytwo {
|
|
MXS_LRADC_DIV_DISABLED = 0,
|
|
MXS_LRADC_DIV_ENABLED,
|
|
};
|
|
|
|
struct mxs_lradc_scale {
|
|
unsigned int integer;
|
|
unsigned int nano;
|
|
};
|
|
|
|
struct mxs_lradc_adc {
|
|
struct mxs_lradc *lradc;
|
|
struct device *dev;
|
|
|
|
void __iomem *base;
|
|
u32 buffer[10];
|
|
struct iio_trigger *trig;
|
|
struct completion completion;
|
|
spinlock_t lock;
|
|
|
|
const u32 *vref_mv;
|
|
struct mxs_lradc_scale scale_avail[LRADC_MAX_TOTAL_CHANS][2];
|
|
unsigned long is_divided;
|
|
};
|
|
|
|
|
|
/* Raw I/O operations */
|
|
static int mxs_lradc_adc_read_single(struct iio_dev *iio_dev, int chan,
|
|
int *val)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio_dev);
|
|
struct mxs_lradc *lradc = adc->lradc;
|
|
int ret;
|
|
|
|
/*
|
|
* See if there is no buffered operation in progress. If there is simply
|
|
* bail out. This can be improved to support both buffered and raw IO at
|
|
* the same time, yet the code becomes horribly complicated. Therefore I
|
|
* applied KISS principle here.
|
|
*/
|
|
ret = iio_device_claim_direct_mode(iio_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
reinit_completion(&adc->completion);
|
|
|
|
/*
|
|
* No buffered operation in progress, map the channel and trigger it.
|
|
* Virtual channel 0 is always used here as the others are always not
|
|
* used if doing raw sampling.
|
|
*/
|
|
if (lradc->soc == IMX28_LRADC)
|
|
writel(LRADC_CTRL1_LRADC_IRQ_EN(0),
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
|
|
writel(0x1, adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
|
|
|
|
/* Enable / disable the divider per requirement */
|
|
if (test_bit(chan, &adc->is_divided))
|
|
writel(1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET,
|
|
adc->base + LRADC_CTRL2 + STMP_OFFSET_REG_SET);
|
|
else
|
|
writel(1 << LRADC_CTRL2_DIVIDE_BY_TWO_OFFSET,
|
|
adc->base + LRADC_CTRL2 + STMP_OFFSET_REG_CLR);
|
|
|
|
/* Clean the slot's previous content, then set new one. */
|
|
writel(LRADC_CTRL4_LRADCSELECT_MASK(0),
|
|
adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_CLR);
|
|
writel(chan, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_SET);
|
|
|
|
writel(0, adc->base + LRADC_CH(0));
|
|
|
|
/* Enable the IRQ and start sampling the channel. */
|
|
writel(LRADC_CTRL1_LRADC_IRQ_EN(0),
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET);
|
|
writel(BIT(0), adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_SET);
|
|
|
|
/* Wait for completion on the channel, 1 second max. */
|
|
ret = wait_for_completion_killable_timeout(&adc->completion, HZ);
|
|
if (!ret)
|
|
ret = -ETIMEDOUT;
|
|
if (ret < 0)
|
|
goto err;
|
|
|
|
/* Read the data. */
|
|
*val = readl(adc->base + LRADC_CH(0)) & LRADC_CH_VALUE_MASK;
|
|
ret = IIO_VAL_INT;
|
|
|
|
err:
|
|
writel(LRADC_CTRL1_LRADC_IRQ_EN(0),
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
|
|
|
|
iio_device_release_direct_mode(iio_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxs_lradc_adc_read_temp(struct iio_dev *iio_dev, int *val)
|
|
{
|
|
int ret, min, max;
|
|
|
|
ret = mxs_lradc_adc_read_single(iio_dev, 8, &min);
|
|
if (ret != IIO_VAL_INT)
|
|
return ret;
|
|
|
|
ret = mxs_lradc_adc_read_single(iio_dev, 9, &max);
|
|
if (ret != IIO_VAL_INT)
|
|
return ret;
|
|
|
|
*val = max - min;
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
static int mxs_lradc_adc_read_raw(struct iio_dev *iio_dev,
|
|
const struct iio_chan_spec *chan,
|
|
int *val, int *val2, long m)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio_dev);
|
|
|
|
switch (m) {
|
|
case IIO_CHAN_INFO_RAW:
|
|
if (chan->type == IIO_TEMP)
|
|
return mxs_lradc_adc_read_temp(iio_dev, val);
|
|
|
|
return mxs_lradc_adc_read_single(iio_dev, chan->channel, val);
|
|
|
|
case IIO_CHAN_INFO_SCALE:
|
|
if (chan->type == IIO_TEMP) {
|
|
/*
|
|
* From the datasheet, we have to multiply by 1.012 and
|
|
* divide by 4
|
|
*/
|
|
*val = 0;
|
|
*val2 = 253000;
|
|
return IIO_VAL_INT_PLUS_MICRO;
|
|
}
|
|
|
|
*val = adc->vref_mv[chan->channel];
|
|
*val2 = chan->scan_type.realbits -
|
|
test_bit(chan->channel, &adc->is_divided);
|
|
return IIO_VAL_FRACTIONAL_LOG2;
|
|
|
|
case IIO_CHAN_INFO_OFFSET:
|
|
if (chan->type == IIO_TEMP) {
|
|
/*
|
|
* The calculated value from the ADC is in Kelvin, we
|
|
* want Celsius for hwmon so the offset is -273.15
|
|
* The offset is applied before scaling so it is
|
|
* actually -213.15 * 4 / 1.012 = -1079.644268
|
|
*/
|
|
*val = -1079;
|
|
*val2 = 644268;
|
|
|
|
return IIO_VAL_INT_PLUS_MICRO;
|
|
}
|
|
|
|
return -EINVAL;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int mxs_lradc_adc_write_raw(struct iio_dev *iio_dev,
|
|
const struct iio_chan_spec *chan,
|
|
int val, int val2, long m)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio_dev);
|
|
struct mxs_lradc_scale *scale_avail =
|
|
adc->scale_avail[chan->channel];
|
|
int ret;
|
|
|
|
ret = iio_device_claim_direct_mode(iio_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (m) {
|
|
case IIO_CHAN_INFO_SCALE:
|
|
ret = -EINVAL;
|
|
if (val == scale_avail[MXS_LRADC_DIV_DISABLED].integer &&
|
|
val2 == scale_avail[MXS_LRADC_DIV_DISABLED].nano) {
|
|
/* divider by two disabled */
|
|
clear_bit(chan->channel, &adc->is_divided);
|
|
ret = 0;
|
|
} else if (val == scale_avail[MXS_LRADC_DIV_ENABLED].integer &&
|
|
val2 == scale_avail[MXS_LRADC_DIV_ENABLED].nano) {
|
|
/* divider by two enabled */
|
|
set_bit(chan->channel, &adc->is_divided);
|
|
ret = 0;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
iio_device_release_direct_mode(iio_dev);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int mxs_lradc_adc_write_raw_get_fmt(struct iio_dev *iio_dev,
|
|
const struct iio_chan_spec *chan,
|
|
long m)
|
|
{
|
|
return IIO_VAL_INT_PLUS_NANO;
|
|
}
|
|
|
|
static ssize_t mxs_lradc_adc_show_scale_avail(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct iio_dev *iio = dev_to_iio_dev(dev);
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
struct iio_dev_attr *iio_attr = to_iio_dev_attr(attr);
|
|
int i, ch, len = 0;
|
|
|
|
ch = iio_attr->address;
|
|
for (i = 0; i < ARRAY_SIZE(adc->scale_avail[ch]); i++)
|
|
len += sprintf(buf + len, "%u.%09u ",
|
|
adc->scale_avail[ch][i].integer,
|
|
adc->scale_avail[ch][i].nano);
|
|
|
|
len += sprintf(buf + len, "\n");
|
|
|
|
return len;
|
|
}
|
|
|
|
#define SHOW_SCALE_AVAILABLE_ATTR(ch)\
|
|
IIO_DEVICE_ATTR(in_voltage##ch##_scale_available, 0444,\
|
|
mxs_lradc_adc_show_scale_avail, NULL, ch)
|
|
|
|
static SHOW_SCALE_AVAILABLE_ATTR(0);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(1);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(2);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(3);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(4);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(5);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(6);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(7);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(10);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(11);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(12);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(13);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(14);
|
|
static SHOW_SCALE_AVAILABLE_ATTR(15);
|
|
|
|
static struct attribute *mxs_lradc_adc_attributes[] = {
|
|
&iio_dev_attr_in_voltage0_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage1_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage2_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage3_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage4_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage5_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage6_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage7_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage10_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage11_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage12_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage13_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage14_scale_available.dev_attr.attr,
|
|
&iio_dev_attr_in_voltage15_scale_available.dev_attr.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group mxs_lradc_adc_attribute_group = {
|
|
.attrs = mxs_lradc_adc_attributes,
|
|
};
|
|
|
|
static const struct iio_info mxs_lradc_adc_iio_info = {
|
|
.read_raw = mxs_lradc_adc_read_raw,
|
|
.write_raw = mxs_lradc_adc_write_raw,
|
|
.write_raw_get_fmt = mxs_lradc_adc_write_raw_get_fmt,
|
|
.attrs = &mxs_lradc_adc_attribute_group,
|
|
};
|
|
|
|
/* IRQ Handling */
|
|
static irqreturn_t mxs_lradc_adc_handle_irq(int irq, void *data)
|
|
{
|
|
struct iio_dev *iio = data;
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
struct mxs_lradc *lradc = adc->lradc;
|
|
unsigned long reg = readl(adc->base + LRADC_CTRL1);
|
|
unsigned long flags;
|
|
|
|
if (!(reg & mxs_lradc_irq_mask(lradc)))
|
|
return IRQ_NONE;
|
|
|
|
if (iio_buffer_enabled(iio)) {
|
|
if (reg & lradc->buffer_vchans) {
|
|
spin_lock_irqsave(&adc->lock, flags);
|
|
iio_trigger_poll(iio->trig);
|
|
spin_unlock_irqrestore(&adc->lock, flags);
|
|
}
|
|
} else if (reg & LRADC_CTRL1_LRADC_IRQ(0)) {
|
|
complete(&adc->completion);
|
|
}
|
|
|
|
writel(reg & mxs_lradc_irq_mask(lradc),
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
/* Trigger handling */
|
|
static irqreturn_t mxs_lradc_adc_trigger_handler(int irq, void *p)
|
|
{
|
|
struct iio_poll_func *pf = p;
|
|
struct iio_dev *iio = pf->indio_dev;
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
const u32 chan_value = LRADC_CH_ACCUMULATE |
|
|
((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET);
|
|
unsigned int i, j = 0;
|
|
|
|
for_each_set_bit(i, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) {
|
|
adc->buffer[j] = readl(adc->base + LRADC_CH(j));
|
|
writel(chan_value, adc->base + LRADC_CH(j));
|
|
adc->buffer[j] &= LRADC_CH_VALUE_MASK;
|
|
adc->buffer[j] /= LRADC_DELAY_TIMER_LOOP;
|
|
j++;
|
|
}
|
|
|
|
iio_push_to_buffers_with_timestamp(iio, adc->buffer, pf->timestamp);
|
|
|
|
iio_trigger_notify_done(iio->trig);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int mxs_lradc_adc_configure_trigger(struct iio_trigger *trig, bool state)
|
|
{
|
|
struct iio_dev *iio = iio_trigger_get_drvdata(trig);
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
const u32 st = state ? STMP_OFFSET_REG_SET : STMP_OFFSET_REG_CLR;
|
|
|
|
writel(LRADC_DELAY_KICK, adc->base + (LRADC_DELAY(0) + st));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct iio_trigger_ops mxs_lradc_adc_trigger_ops = {
|
|
.set_trigger_state = &mxs_lradc_adc_configure_trigger,
|
|
};
|
|
|
|
static int mxs_lradc_adc_trigger_init(struct iio_dev *iio)
|
|
{
|
|
int ret;
|
|
struct iio_trigger *trig;
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
|
|
trig = devm_iio_trigger_alloc(&iio->dev, "%s-dev%i", iio->name,
|
|
iio->id);
|
|
if (!trig)
|
|
return -ENOMEM;
|
|
|
|
trig->dev.parent = adc->dev;
|
|
iio_trigger_set_drvdata(trig, iio);
|
|
trig->ops = &mxs_lradc_adc_trigger_ops;
|
|
|
|
ret = iio_trigger_register(trig);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adc->trig = trig;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mxs_lradc_adc_trigger_remove(struct iio_dev *iio)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
|
|
iio_trigger_unregister(adc->trig);
|
|
}
|
|
|
|
static int mxs_lradc_adc_buffer_preenable(struct iio_dev *iio)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
struct mxs_lradc *lradc = adc->lradc;
|
|
int chan, ofs = 0;
|
|
unsigned long enable = 0;
|
|
u32 ctrl4_set = 0;
|
|
u32 ctrl4_clr = 0;
|
|
u32 ctrl1_irq = 0;
|
|
const u32 chan_value = LRADC_CH_ACCUMULATE |
|
|
((LRADC_DELAY_TIMER_LOOP - 1) << LRADC_CH_NUM_SAMPLES_OFFSET);
|
|
|
|
if (lradc->soc == IMX28_LRADC)
|
|
writel(lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET,
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
|
|
writel(lradc->buffer_vchans,
|
|
adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
|
|
|
|
for_each_set_bit(chan, iio->active_scan_mask, LRADC_MAX_TOTAL_CHANS) {
|
|
ctrl4_set |= chan << LRADC_CTRL4_LRADCSELECT_OFFSET(ofs);
|
|
ctrl4_clr |= LRADC_CTRL4_LRADCSELECT_MASK(ofs);
|
|
ctrl1_irq |= LRADC_CTRL1_LRADC_IRQ_EN(ofs);
|
|
writel(chan_value, adc->base + LRADC_CH(ofs));
|
|
bitmap_set(&enable, ofs, 1);
|
|
ofs++;
|
|
}
|
|
|
|
writel(LRADC_DELAY_TRIGGER_LRADCS_MASK | LRADC_DELAY_KICK,
|
|
adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_CLR);
|
|
writel(ctrl4_clr, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_CLR);
|
|
writel(ctrl4_set, adc->base + LRADC_CTRL4 + STMP_OFFSET_REG_SET);
|
|
writel(ctrl1_irq, adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_SET);
|
|
writel(enable << LRADC_DELAY_TRIGGER_LRADCS_OFFSET,
|
|
adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_SET);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mxs_lradc_adc_buffer_postdisable(struct iio_dev *iio)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
struct mxs_lradc *lradc = adc->lradc;
|
|
|
|
writel(LRADC_DELAY_TRIGGER_LRADCS_MASK | LRADC_DELAY_KICK,
|
|
adc->base + LRADC_DELAY(0) + STMP_OFFSET_REG_CLR);
|
|
|
|
writel(lradc->buffer_vchans,
|
|
adc->base + LRADC_CTRL0 + STMP_OFFSET_REG_CLR);
|
|
if (lradc->soc == IMX28_LRADC)
|
|
writel(lradc->buffer_vchans << LRADC_CTRL1_LRADC_IRQ_EN_OFFSET,
|
|
adc->base + LRADC_CTRL1 + STMP_OFFSET_REG_CLR);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool mxs_lradc_adc_validate_scan_mask(struct iio_dev *iio,
|
|
const unsigned long *mask)
|
|
{
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
struct mxs_lradc *lradc = adc->lradc;
|
|
const int map_chans = bitmap_weight(mask, LRADC_MAX_TOTAL_CHANS);
|
|
int rsvd_chans = 0;
|
|
unsigned long rsvd_mask = 0;
|
|
|
|
if (lradc->use_touchbutton)
|
|
rsvd_mask |= CHAN_MASK_TOUCHBUTTON;
|
|
if (lradc->touchscreen_wire == MXS_LRADC_TOUCHSCREEN_4WIRE)
|
|
rsvd_mask |= CHAN_MASK_TOUCHSCREEN_4WIRE;
|
|
if (lradc->touchscreen_wire == MXS_LRADC_TOUCHSCREEN_5WIRE)
|
|
rsvd_mask |= CHAN_MASK_TOUCHSCREEN_5WIRE;
|
|
|
|
if (lradc->use_touchbutton)
|
|
rsvd_chans++;
|
|
if (lradc->touchscreen_wire)
|
|
rsvd_chans += 2;
|
|
|
|
/* Test for attempts to map channels with special mode of operation. */
|
|
if (bitmap_intersects(mask, &rsvd_mask, LRADC_MAX_TOTAL_CHANS))
|
|
return false;
|
|
|
|
/* Test for attempts to map more channels then available slots. */
|
|
if (map_chans + rsvd_chans > LRADC_MAX_MAPPED_CHANS)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static const struct iio_buffer_setup_ops mxs_lradc_adc_buffer_ops = {
|
|
.preenable = &mxs_lradc_adc_buffer_preenable,
|
|
.postenable = &iio_triggered_buffer_postenable,
|
|
.predisable = &iio_triggered_buffer_predisable,
|
|
.postdisable = &mxs_lradc_adc_buffer_postdisable,
|
|
.validate_scan_mask = &mxs_lradc_adc_validate_scan_mask,
|
|
};
|
|
|
|
/* Driver initialization */
|
|
#define MXS_ADC_CHAN(idx, chan_type, name) { \
|
|
.type = (chan_type), \
|
|
.indexed = 1, \
|
|
.scan_index = (idx), \
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
|
|
BIT(IIO_CHAN_INFO_SCALE), \
|
|
.channel = (idx), \
|
|
.address = (idx), \
|
|
.scan_type = { \
|
|
.sign = 'u', \
|
|
.realbits = LRADC_RESOLUTION, \
|
|
.storagebits = 32, \
|
|
}, \
|
|
.datasheet_name = (name), \
|
|
}
|
|
|
|
static const struct iio_chan_spec mx23_lradc_chan_spec[] = {
|
|
MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"),
|
|
MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"),
|
|
MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"),
|
|
MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"),
|
|
MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"),
|
|
MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"),
|
|
MXS_ADC_CHAN(6, IIO_VOLTAGE, "VDDIO"),
|
|
MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"),
|
|
/* Combined Temperature sensors */
|
|
{
|
|
.type = IIO_TEMP,
|
|
.indexed = 1,
|
|
.scan_index = 8,
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
|
BIT(IIO_CHAN_INFO_OFFSET) |
|
|
BIT(IIO_CHAN_INFO_SCALE),
|
|
.channel = 8,
|
|
.scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,},
|
|
.datasheet_name = "TEMP_DIE",
|
|
},
|
|
/* Hidden channel to keep indexes */
|
|
{
|
|
.type = IIO_TEMP,
|
|
.indexed = 1,
|
|
.scan_index = -1,
|
|
.channel = 9,
|
|
},
|
|
MXS_ADC_CHAN(10, IIO_VOLTAGE, NULL),
|
|
MXS_ADC_CHAN(11, IIO_VOLTAGE, NULL),
|
|
MXS_ADC_CHAN(12, IIO_VOLTAGE, "USB_DP"),
|
|
MXS_ADC_CHAN(13, IIO_VOLTAGE, "USB_DN"),
|
|
MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"),
|
|
MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"),
|
|
};
|
|
|
|
static const struct iio_chan_spec mx28_lradc_chan_spec[] = {
|
|
MXS_ADC_CHAN(0, IIO_VOLTAGE, "LRADC0"),
|
|
MXS_ADC_CHAN(1, IIO_VOLTAGE, "LRADC1"),
|
|
MXS_ADC_CHAN(2, IIO_VOLTAGE, "LRADC2"),
|
|
MXS_ADC_CHAN(3, IIO_VOLTAGE, "LRADC3"),
|
|
MXS_ADC_CHAN(4, IIO_VOLTAGE, "LRADC4"),
|
|
MXS_ADC_CHAN(5, IIO_VOLTAGE, "LRADC5"),
|
|
MXS_ADC_CHAN(6, IIO_VOLTAGE, "LRADC6"),
|
|
MXS_ADC_CHAN(7, IIO_VOLTAGE, "VBATT"),
|
|
/* Combined Temperature sensors */
|
|
{
|
|
.type = IIO_TEMP,
|
|
.indexed = 1,
|
|
.scan_index = 8,
|
|
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
|
|
BIT(IIO_CHAN_INFO_OFFSET) |
|
|
BIT(IIO_CHAN_INFO_SCALE),
|
|
.channel = 8,
|
|
.scan_type = {.sign = 'u', .realbits = 18, .storagebits = 32,},
|
|
.datasheet_name = "TEMP_DIE",
|
|
},
|
|
/* Hidden channel to keep indexes */
|
|
{
|
|
.type = IIO_TEMP,
|
|
.indexed = 1,
|
|
.scan_index = -1,
|
|
.channel = 9,
|
|
},
|
|
MXS_ADC_CHAN(10, IIO_VOLTAGE, "VDDIO"),
|
|
MXS_ADC_CHAN(11, IIO_VOLTAGE, "VTH"),
|
|
MXS_ADC_CHAN(12, IIO_VOLTAGE, "VDDA"),
|
|
MXS_ADC_CHAN(13, IIO_VOLTAGE, "VDDD"),
|
|
MXS_ADC_CHAN(14, IIO_VOLTAGE, "VBG"),
|
|
MXS_ADC_CHAN(15, IIO_VOLTAGE, "VDD5V"),
|
|
};
|
|
|
|
static void mxs_lradc_adc_hw_init(struct mxs_lradc_adc *adc)
|
|
{
|
|
/* The ADC always uses DELAY CHANNEL 0. */
|
|
const u32 adc_cfg =
|
|
(1 << (LRADC_DELAY_TRIGGER_DELAYS_OFFSET + 0)) |
|
|
(LRADC_DELAY_TIMER_PER << LRADC_DELAY_DELAY_OFFSET);
|
|
|
|
/* Configure DELAY CHANNEL 0 for generic ADC sampling. */
|
|
writel(adc_cfg, adc->base + LRADC_DELAY(0));
|
|
|
|
/*
|
|
* Start internal temperature sensing by clearing bit
|
|
* HW_LRADC_CTRL2_TEMPSENSE_PWD. This bit can be left cleared
|
|
* after power up.
|
|
*/
|
|
writel(0, adc->base + LRADC_CTRL2);
|
|
}
|
|
|
|
static void mxs_lradc_adc_hw_stop(struct mxs_lradc_adc *adc)
|
|
{
|
|
writel(0, adc->base + LRADC_DELAY(0));
|
|
}
|
|
|
|
static int mxs_lradc_adc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct mxs_lradc *lradc = dev_get_drvdata(dev->parent);
|
|
struct mxs_lradc_adc *adc;
|
|
struct iio_dev *iio;
|
|
struct resource *iores;
|
|
int ret, irq, virq, i, s, n;
|
|
u64 scale_uv;
|
|
const char **irq_name;
|
|
|
|
/* Allocate the IIO device. */
|
|
iio = devm_iio_device_alloc(dev, sizeof(*adc));
|
|
if (!iio) {
|
|
dev_err(dev, "Failed to allocate IIO device\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
adc = iio_priv(iio);
|
|
adc->lradc = lradc;
|
|
adc->dev = dev;
|
|
|
|
iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!iores)
|
|
return -EINVAL;
|
|
|
|
adc->base = devm_ioremap(dev, iores->start, resource_size(iores));
|
|
if (!adc->base)
|
|
return -ENOMEM;
|
|
|
|
init_completion(&adc->completion);
|
|
spin_lock_init(&adc->lock);
|
|
|
|
platform_set_drvdata(pdev, iio);
|
|
|
|
iio->name = pdev->name;
|
|
iio->dev.parent = dev;
|
|
iio->dev.of_node = dev->parent->of_node;
|
|
iio->info = &mxs_lradc_adc_iio_info;
|
|
iio->modes = INDIO_DIRECT_MODE;
|
|
iio->masklength = LRADC_MAX_TOTAL_CHANS;
|
|
|
|
if (lradc->soc == IMX23_LRADC) {
|
|
iio->channels = mx23_lradc_chan_spec;
|
|
iio->num_channels = ARRAY_SIZE(mx23_lradc_chan_spec);
|
|
irq_name = mx23_lradc_adc_irq_names;
|
|
n = ARRAY_SIZE(mx23_lradc_adc_irq_names);
|
|
} else {
|
|
iio->channels = mx28_lradc_chan_spec;
|
|
iio->num_channels = ARRAY_SIZE(mx28_lradc_chan_spec);
|
|
irq_name = mx28_lradc_adc_irq_names;
|
|
n = ARRAY_SIZE(mx28_lradc_adc_irq_names);
|
|
}
|
|
|
|
ret = stmp_reset_block(adc->base);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
irq = platform_get_irq_byname(pdev, irq_name[i]);
|
|
if (irq < 0)
|
|
return irq;
|
|
|
|
virq = irq_of_parse_and_map(dev->parent->of_node, irq);
|
|
|
|
ret = devm_request_irq(dev, virq, mxs_lradc_adc_handle_irq,
|
|
0, irq_name[i], iio);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = mxs_lradc_adc_trigger_init(iio);
|
|
if (ret)
|
|
goto err_trig;
|
|
|
|
ret = iio_triggered_buffer_setup(iio, &iio_pollfunc_store_time,
|
|
&mxs_lradc_adc_trigger_handler,
|
|
&mxs_lradc_adc_buffer_ops);
|
|
if (ret)
|
|
return ret;
|
|
|
|
adc->vref_mv = mxs_lradc_adc_vref_mv[lradc->soc];
|
|
|
|
/* Populate available ADC input ranges */
|
|
for (i = 0; i < LRADC_MAX_TOTAL_CHANS; i++) {
|
|
for (s = 0; s < ARRAY_SIZE(adc->scale_avail[i]); s++) {
|
|
/*
|
|
* [s=0] = optional divider by two disabled (default)
|
|
* [s=1] = optional divider by two enabled
|
|
*
|
|
* The scale is calculated by doing:
|
|
* Vref >> (realbits - s)
|
|
* which multiplies by two on the second component
|
|
* of the array.
|
|
*/
|
|
scale_uv = ((u64)adc->vref_mv[i] * 100000000) >>
|
|
(LRADC_RESOLUTION - s);
|
|
adc->scale_avail[i][s].nano =
|
|
do_div(scale_uv, 100000000) * 10;
|
|
adc->scale_avail[i][s].integer = scale_uv;
|
|
}
|
|
}
|
|
|
|
/* Configure the hardware. */
|
|
mxs_lradc_adc_hw_init(adc);
|
|
|
|
/* Register IIO device. */
|
|
ret = iio_device_register(iio);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to register IIO device\n");
|
|
goto err_dev;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_dev:
|
|
mxs_lradc_adc_hw_stop(adc);
|
|
mxs_lradc_adc_trigger_remove(iio);
|
|
err_trig:
|
|
iio_triggered_buffer_cleanup(iio);
|
|
return ret;
|
|
}
|
|
|
|
static int mxs_lradc_adc_remove(struct platform_device *pdev)
|
|
{
|
|
struct iio_dev *iio = platform_get_drvdata(pdev);
|
|
struct mxs_lradc_adc *adc = iio_priv(iio);
|
|
|
|
iio_device_unregister(iio);
|
|
mxs_lradc_adc_hw_stop(adc);
|
|
mxs_lradc_adc_trigger_remove(iio);
|
|
iio_triggered_buffer_cleanup(iio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver mxs_lradc_adc_driver = {
|
|
.driver = {
|
|
.name = "mxs-lradc-adc",
|
|
},
|
|
.probe = mxs_lradc_adc_probe,
|
|
.remove = mxs_lradc_adc_remove,
|
|
};
|
|
module_platform_driver(mxs_lradc_adc_driver);
|
|
|
|
MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
|
|
MODULE_DESCRIPTION("Freescale MXS LRADC driver general purpose ADC driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_ALIAS("platform:mxs-lradc-adc");
|