// SPDX-License-Identifier: GPL-2.0
/*
* LTC2688 16 channel, 16 bit Voltage Output SoftSpan DAC driver
*
* Copyright 2022 Analog Devices Inc.
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/iio/iio.h>
#include <linux/limits.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#define LTC2688_DAC_CHANNELS 16
#define LTC2688_CMD_CH_CODE(x) (0x00 + (x))
#define LTC2688_CMD_CH_SETTING(x) (0x10 + (x))
#define LTC2688_CMD_CH_OFFSET(x) (0X20 + (x))
#define LTC2688_CMD_CH_GAIN(x) (0x30 + (x))
#define LTC2688_CMD_CH_CODE_UPDATE(x) (0x40 + (x))
#define LTC2688_CMD_CONFIG 0x70
#define LTC2688_CMD_POWERDOWN 0x71
#define LTC2688_CMD_A_B_SELECT 0x72
#define LTC2688_CMD_SW_TOGGLE 0x73
#define LTC2688_CMD_TOGGLE_DITHER_EN 0x74
#define LTC2688_CMD_THERMAL_STAT 0x77
#define LTC2688_CMD_UPDATE_ALL 0x7C
#define LTC2688_CMD_NOOP 0xFF
#define LTC2688_READ_OPERATION 0x80
/* Channel Settings */
#define LTC2688_CH_SPAN_MSK GENMASK(2, 0)
#define LTC2688_CH_OVERRANGE_MSK BIT(3)
#define LTC2688_CH_TD_SEL_MSK GENMASK(5, 4)
#define LTC2688_CH_TGP_MAX 3
#define LTC2688_CH_DIT_PER_MSK GENMASK(8, 6)
#define LTC2688_CH_DIT_PH_MSK GENMASK(10, 9)
#define LTC2688_CH_MODE_MSK BIT(11)
#define LTC2688_DITHER_RAW_MASK GENMASK(15, 2)
#define LTC2688_CH_CALIBBIAS_MASK GENMASK(15, 2)
#define LTC2688_DITHER_RAW_MAX_VAL (BIT(14) - 1)
#define LTC2688_CH_CALIBBIAS_MAX_VAL (BIT(14) - 1)
/* Configuration register */
#define LTC2688_CONFIG_RST BIT(15)
#define LTC2688_CONFIG_EXT_REF BIT(1)
#define LTC2688_DITHER_FREQ_AVAIL_N 5
enum {
LTC2688_SPAN_RANGE_0V_5V,
LTC2688_SPAN_RANGE_0V_10V,
LTC2688_SPAN_RANGE_M5V_5V,
LTC2688_SPAN_RANGE_M10V_10V,
LTC2688_SPAN_RANGE_M15V_15V,
LTC2688_SPAN_RANGE_MAX
};
enum {
LTC2688_MODE_DEFAULT,
LTC2688_MODE_DITHER_TOGGLE,
};
struct ltc2688_chan {
long dither_frequency[LTC2688_DITHER_FREQ_AVAIL_N];
bool overrange;
bool toggle_chan;
u8 mode;
};
struct ltc2688_state {
struct spi_device *spi;
struct regmap *regmap;
struct ltc2688_chan channels[LTC2688_DAC_CHANNELS];
struct iio_chan_spec *iio_chan;
/* lock to protect against multiple access to the device and shared data */
struct mutex lock;
int vref;
/*
* DMA (thus cache coherency maintenance) may require the
* transfer buffers to live in their own cache lines.
*/
u8 tx_data[6] __aligned(IIO_DMA_MINALIGN);
u8 rx_data[3];
};
static int ltc2688_spi_read(void *context, const void *reg, size_t reg_size,
void *val, size_t val_size)
{
struct ltc2688_state *st = context;
struct spi_transfer xfers[] = {
{
.tx_buf = st->tx_data,
.bits_per_word = 8,
.len = reg_size + val_size,
.cs_change = 1,
}, {
.tx_buf = st->tx_data + 3,
.rx_buf = st->rx_data,
.bits_per_word = 8,
.len = reg_size + val_size,
},
};
int ret;
memcpy(st->tx_data, reg, reg_size);
ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers));
if (ret)
return ret;
memcpy(val, &st->rx_data[1], val_size);
return 0;
}
static int ltc2688_spi_write(void *context, const void *data, size_t count)
{
struct ltc2688_state *st = context;
return spi_write(st->spi, data, count);
}
static int ltc2688_span_get(const struct ltc2688_state *st, int c)
{
int ret, reg, span;
ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(c), ®);
if (ret)
return ret;
span = FIELD_GET(LTC2688_CH_SPAN_MSK, reg);
/* sanity check to make sure we don't get any weird value from the HW */
if (span >= LTC2688_SPAN_RANGE_MAX)
return -EIO;
return span;
}
static const int ltc2688_span_helper[LTC2688_SPAN_RANGE_MAX][2] = {
{0, 5000}, {0, 10000}, {-5000, 5000}, {-10000, 10000}, {-15000, 15000},
};
static int ltc2688_scale_get(const struct ltc2688_state *st, int c, int *val)
{
const struct ltc2688_chan *chan = &st->channels[c];
int span, fs;
span = ltc2688_span_get(st, c);
if (span < 0)
return span;
fs = ltc2688_span_helper[span][1] - ltc2688_span_helper[span][0];
if (chan->overrange)
fs = mult_frac(fs, 105, 100);
*val = DIV_ROUND_CLOSEST(fs * st->vref, 4096);
return 0;
}
static int ltc2688_offset_get(const struct ltc2688_state *st, int c, int *val)
{
int span;
span = ltc2688_span_get(st, c);
if (span < 0)
return span;
if (ltc2688_span_helper[span][0] < 0)
*val = -32768;
else
*val = 0;
return 0;
}
enum {
LTC2688_INPUT_A,
LTC2688_INPUT_B,
LTC2688_INPUT_B_AVAIL,
LTC2688_DITHER_OFF,
LTC2688_DITHER_FREQ_AVAIL,
};
static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input,
u16 code)
{
struct ltc2688_chan *c = &st->channels[chan];
int ret, reg;
/* 2 LSBs set to 0 if writing dither amplitude */
if (!c->toggle_chan && input == LTC2688_INPUT_B) {
if (code > LTC2688_DITHER_RAW_MAX_VAL)