mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 13:55:32 +00:00
![Lars-Peter Clausen](/assets/img/avatar_default.png)
The Xilinx XADC is a ADC that can be found in the series 7 FPGAs from Xilinx. The XADC has a DRP interface for communication. Currently two different frontends for the DRP interface exist. One that is only available on the ZYNQ family as a hardmacro in the SoC portion of the ZYNQ. The other one is available on all series 7 platforms and is a softmacro with a AXI interface. This driver supports both interfaces and internally has a small abstraction layer that hides the specifics of these interfaces from the main driver logic. The ADC has a couple of internal channels which are used for voltage and temperature monitoring of the FPGA as well as one primary and up to 16 channels auxiliary channels for measuring external voltages. The external auxiliary channels can either be directly connected each to one physical pin on the FPGA or they can make use of an external multiplexer which is responsible for multiplexing the external signals onto one pair of physical pins. The voltage and temperature monitoring channels also have an event capability which allows to generate a interrupt when their value falls below or raises above a set threshold. Buffered sampling mode is supported by the driver, but only for AXI-XADC since the ZYNQ XADC interface does not have capabilities for supporting buffer mode (no end-of-conversion interrupt). If buffered mode is supported the driver will register two triggers. One "xadc-samplerate" trigger which will generate samples with the configured samplerate. And one "xadc-convst" trigger which will generate one sample each time the CONVST (conversion start) signal is asserted. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Jonathan Cameron <jic23@kernel.org>
254 lines
5.8 KiB
C
254 lines
5.8 KiB
C
/*
|
|
* Xilinx XADC driver
|
|
*
|
|
* Copyright 2013 Analog Devices Inc.
|
|
* Author: Lars-Peter Clauen <lars@metafoo.de>
|
|
*
|
|
* Licensed under the GPL-2.
|
|
*/
|
|
|
|
#include <linux/iio/events.h>
|
|
#include <linux/iio/iio.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include "xilinx-xadc.h"
|
|
|
|
static const struct iio_chan_spec *xadc_event_to_channel(
|
|
struct iio_dev *indio_dev, unsigned int event)
|
|
{
|
|
switch (event) {
|
|
case XADC_THRESHOLD_OT_MAX:
|
|
case XADC_THRESHOLD_TEMP_MAX:
|
|
return &indio_dev->channels[0];
|
|
case XADC_THRESHOLD_VCCINT_MAX:
|
|
case XADC_THRESHOLD_VCCAUX_MAX:
|
|
return &indio_dev->channels[event];
|
|
default:
|
|
return &indio_dev->channels[event-1];
|
|
}
|
|
}
|
|
|
|
static void xadc_handle_event(struct iio_dev *indio_dev, unsigned int event)
|
|
{
|
|
const struct iio_chan_spec *chan;
|
|
unsigned int offset;
|
|
|
|
/* Temperature threshold error, we don't handle this yet */
|
|
if (event == 0)
|
|
return;
|
|
|
|
if (event < 4)
|
|
offset = event;
|
|
else
|
|
offset = event + 4;
|
|
|
|
chan = xadc_event_to_channel(indio_dev, event);
|
|
|
|
if (chan->type == IIO_TEMP) {
|
|
/*
|
|
* The temperature channel only supports over-temperature
|
|
* events.
|
|
*/
|
|
iio_push_event(indio_dev,
|
|
IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
|
|
IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING),
|
|
iio_get_time_ns());
|
|
} else {
|
|
/*
|
|
* For other channels we don't know whether it is a upper or
|
|
* lower threshold event. Userspace will have to check the
|
|
* channel value if it wants to know.
|
|
*/
|
|
iio_push_event(indio_dev,
|
|
IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
|
|
IIO_EV_TYPE_THRESH, IIO_EV_DIR_EITHER),
|
|
iio_get_time_ns());
|
|
}
|
|
}
|
|
|
|
void xadc_handle_events(struct iio_dev *indio_dev, unsigned long events)
|
|
{
|
|
unsigned int i;
|
|
|
|
for_each_set_bit(i, &events, 8)
|
|
xadc_handle_event(indio_dev, i);
|
|
}
|
|
|
|
static unsigned xadc_get_threshold_offset(const struct iio_chan_spec *chan,
|
|
enum iio_event_direction dir)
|
|
{
|
|
unsigned int offset;
|
|
|
|
if (chan->type == IIO_TEMP) {
|
|
offset = XADC_THRESHOLD_OT_MAX;
|
|
} else {
|
|
if (chan->channel < 2)
|
|
offset = chan->channel + 1;
|
|
else
|
|
offset = chan->channel + 6;
|
|
}
|
|
|
|
if (dir == IIO_EV_DIR_FALLING)
|
|
offset += 4;
|
|
|
|
return offset;
|
|
}
|
|
|
|
static unsigned int xadc_get_alarm_mask(const struct iio_chan_spec *chan)
|
|
{
|
|
if (chan->type == IIO_TEMP) {
|
|
return XADC_ALARM_OT_MASK;
|
|
} else {
|
|
switch (chan->channel) {
|
|
case 0:
|
|
return XADC_ALARM_VCCINT_MASK;
|
|
case 1:
|
|
return XADC_ALARM_VCCAUX_MASK;
|
|
case 2:
|
|
return XADC_ALARM_VCCBRAM_MASK;
|
|
case 3:
|
|
return XADC_ALARM_VCCPINT_MASK;
|
|
case 4:
|
|
return XADC_ALARM_VCCPAUX_MASK;
|
|
case 5:
|
|
return XADC_ALARM_VCCODDR_MASK;
|
|
default:
|
|
/* We will never get here */
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
int xadc_read_event_config(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan, enum iio_event_type type,
|
|
enum iio_event_direction dir)
|
|
{
|
|
struct xadc *xadc = iio_priv(indio_dev);
|
|
|
|
return (bool)(xadc->alarm_mask & xadc_get_alarm_mask(chan));
|
|
}
|
|
|
|
int xadc_write_event_config(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan, enum iio_event_type type,
|
|
enum iio_event_direction dir, int state)
|
|
{
|
|
unsigned int alarm = xadc_get_alarm_mask(chan);
|
|
struct xadc *xadc = iio_priv(indio_dev);
|
|
uint16_t cfg, old_cfg;
|
|
int ret;
|
|
|
|
mutex_lock(&xadc->mutex);
|
|
|
|
if (state)
|
|
xadc->alarm_mask |= alarm;
|
|
else
|
|
xadc->alarm_mask &= ~alarm;
|
|
|
|
xadc->ops->update_alarm(xadc, xadc->alarm_mask);
|
|
|
|
ret = _xadc_read_adc_reg(xadc, XADC_REG_CONF1, &cfg);
|
|
if (ret)
|
|
goto err_out;
|
|
|
|
old_cfg = cfg;
|
|
cfg |= XADC_CONF1_ALARM_MASK;
|
|
cfg &= ~((xadc->alarm_mask & 0xf0) << 4); /* bram, pint, paux, ddr */
|
|
cfg &= ~((xadc->alarm_mask & 0x08) >> 3); /* ot */
|
|
cfg &= ~((xadc->alarm_mask & 0x07) << 1); /* temp, vccint, vccaux */
|
|
if (old_cfg != cfg)
|
|
ret = _xadc_write_adc_reg(xadc, XADC_REG_CONF1, cfg);
|
|
|
|
err_out:
|
|
mutex_unlock(&xadc->mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Register value is msb aligned, the lower 4 bits are ignored */
|
|
#define XADC_THRESHOLD_VALUE_SHIFT 4
|
|
|
|
int xadc_read_event_value(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan, enum iio_event_type type,
|
|
enum iio_event_direction dir, enum iio_event_info info,
|
|
int *val, int *val2)
|
|
{
|
|
unsigned int offset = xadc_get_threshold_offset(chan, dir);
|
|
struct xadc *xadc = iio_priv(indio_dev);
|
|
|
|
switch (info) {
|
|
case IIO_EV_INFO_VALUE:
|
|
*val = xadc->threshold[offset];
|
|
break;
|
|
case IIO_EV_INFO_HYSTERESIS:
|
|
*val = xadc->temp_hysteresis;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
*val >>= XADC_THRESHOLD_VALUE_SHIFT;
|
|
|
|
return IIO_VAL_INT;
|
|
}
|
|
|
|
int xadc_write_event_value(struct iio_dev *indio_dev,
|
|
const struct iio_chan_spec *chan, enum iio_event_type type,
|
|
enum iio_event_direction dir, enum iio_event_info info,
|
|
int val, int val2)
|
|
{
|
|
unsigned int offset = xadc_get_threshold_offset(chan, dir);
|
|
struct xadc *xadc = iio_priv(indio_dev);
|
|
int ret = 0;
|
|
|
|
val <<= XADC_THRESHOLD_VALUE_SHIFT;
|
|
|
|
if (val < 0 || val > 0xffff)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&xadc->mutex);
|
|
|
|
switch (info) {
|
|
case IIO_EV_INFO_VALUE:
|
|
xadc->threshold[offset] = val;
|
|
break;
|
|
case IIO_EV_INFO_HYSTERESIS:
|
|
xadc->temp_hysteresis = val;
|
|
break;
|
|
default:
|
|
mutex_unlock(&xadc->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chan->type == IIO_TEMP) {
|
|
/*
|
|
* According to the datasheet we need to set the lower 4 bits to
|
|
* 0x3, otherwise 125 degree celsius will be used as the
|
|
* threshold.
|
|
*/
|
|
val |= 0x3;
|
|
|
|
/*
|
|
* Since we store the hysteresis as relative (to the threshold)
|
|
* value, but the hardware expects an absolute value we need to
|
|
* recalcualte this value whenever the hysteresis or the
|
|
* threshold changes.
|
|
*/
|
|
if (xadc->threshold[offset] < xadc->temp_hysteresis)
|
|
xadc->threshold[offset + 4] = 0;
|
|
else
|
|
xadc->threshold[offset + 4] = xadc->threshold[offset] -
|
|
xadc->temp_hysteresis;
|
|
ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset + 4),
|
|
xadc->threshold[offset + 4]);
|
|
if (ret)
|
|
goto out_unlock;
|
|
}
|
|
|
|
if (info == IIO_EV_INFO_VALUE)
|
|
ret = _xadc_write_adc_reg(xadc, XADC_REG_THRESHOLD(offset), val);
|
|
|
|
out_unlock:
|
|
mutex_unlock(&xadc->mutex);
|
|
|
|
return ret;
|
|
}
|