| // 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 regulator_bulk_data regulators[2]; |
| 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) |
| return -EINVAL; |
| |
| code = FIELD_PREP(LTC2688_DITHER_RAW_MASK, code); |
| } |
| |
| mutex_lock(&st->lock); |
| /* select the correct input register to read from */ |
| ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), |
| input << chan); |
| if (ret) |
| goto out_unlock; |
| |
| /* |
| * If in dither/toggle mode the dac should be updated by an |
| * external signal (or sw toggle) and not here. |
| */ |
| if (c->mode == LTC2688_MODE_DEFAULT) |
| reg = LTC2688_CMD_CH_CODE_UPDATE(chan); |
| else |
| reg = LTC2688_CMD_CH_CODE(chan); |
| |
| ret = regmap_write(st->regmap, reg, code); |
| out_unlock: |
| mutex_unlock(&st->lock); |
| return ret; |
| } |
| |
| static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input, |
| u32 *code) |
| { |
| struct ltc2688_chan *c = &st->channels[chan]; |
| int ret; |
| |
| mutex_lock(&st->lock); |
| ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), |
| input << chan); |
| if (ret) |
| goto out_unlock; |
| |
| ret = regmap_read(st->regmap, LTC2688_CMD_CH_CODE(chan), code); |
| out_unlock: |
| mutex_unlock(&st->lock); |
| |
| if (!c->toggle_chan && input == LTC2688_INPUT_B) |
| *code = FIELD_GET(LTC2688_DITHER_RAW_MASK, *code); |
| |
| return ret; |
| } |
| |
| static const int ltc2688_raw_range[] = {0, 1, U16_MAX}; |
| |
| static int ltc2688_read_avail(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, |
| const int **vals, int *type, int *length, |
| long info) |
| { |
| switch (info) { |
| case IIO_CHAN_INFO_RAW: |
| *vals = ltc2688_raw_range; |
| *type = IIO_VAL_INT; |
| return IIO_AVAIL_RANGE; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ltc2688_read_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int *val, |
| int *val2, long info) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| int ret; |
| |
| switch (info) { |
| case IIO_CHAN_INFO_RAW: |
| ret = ltc2688_dac_code_read(st, chan->channel, LTC2688_INPUT_A, |
| val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| case IIO_CHAN_INFO_OFFSET: |
| ret = ltc2688_offset_get(st, chan->channel, val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| case IIO_CHAN_INFO_SCALE: |
| ret = ltc2688_scale_get(st, chan->channel, val); |
| if (ret) |
| return ret; |
| |
| *val2 = 16; |
| return IIO_VAL_FRACTIONAL_LOG2; |
| case IIO_CHAN_INFO_CALIBBIAS: |
| ret = regmap_read(st->regmap, |
| LTC2688_CMD_CH_OFFSET(chan->channel), val); |
| if (ret) |
| return ret; |
| |
| *val = FIELD_GET(LTC2688_CH_CALIBBIAS_MASK, *val); |
| return IIO_VAL_INT; |
| case IIO_CHAN_INFO_CALIBSCALE: |
| ret = regmap_read(st->regmap, |
| LTC2688_CMD_CH_GAIN(chan->channel), val); |
| if (ret) |
| return ret; |
| |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ltc2688_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int val, |
| int val2, long info) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| |
| switch (info) { |
| case IIO_CHAN_INFO_RAW: |
| if (val > U16_MAX || val < 0) |
| return -EINVAL; |
| |
| return ltc2688_dac_code_write(st, chan->channel, |
| LTC2688_INPUT_A, val); |
| case IIO_CHAN_INFO_CALIBBIAS: |
| if (val > LTC2688_CH_CALIBBIAS_MAX_VAL) |
| return -EINVAL; |
| |
| return regmap_write(st->regmap, |
| LTC2688_CMD_CH_OFFSET(chan->channel), |
| FIELD_PREP(LTC2688_CH_CALIBBIAS_MASK, val)); |
| case IIO_CHAN_INFO_CALIBSCALE: |
| return regmap_write(st->regmap, |
| LTC2688_CMD_CH_GAIN(chan->channel), val); |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static ssize_t ltc2688_dither_toggle_set(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| struct ltc2688_chan *c = &st->channels[chan->channel]; |
| int ret; |
| bool en; |
| |
| ret = kstrtobool(buf, &en); |
| if (ret) |
| return ret; |
| |
| mutex_lock(&st->lock); |
| ret = regmap_update_bits(st->regmap, LTC2688_CMD_TOGGLE_DITHER_EN, |
| BIT(chan->channel), en << chan->channel); |
| if (ret) |
| goto out_unlock; |
| |
| c->mode = en ? LTC2688_MODE_DITHER_TOGGLE : LTC2688_MODE_DEFAULT; |
| out_unlock: |
| mutex_unlock(&st->lock); |
| |
| return ret ?: len; |
| } |
| |
| static ssize_t ltc2688_reg_bool_get(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| char *buf) |
| { |
| const struct ltc2688_state *st = iio_priv(indio_dev); |
| int ret; |
| u32 val; |
| |
| ret = regmap_read(st->regmap, private, &val); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%u\n", !!(val & BIT(chan->channel))); |
| } |
| |
| static ssize_t ltc2688_reg_bool_set(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| const struct ltc2688_state *st = iio_priv(indio_dev); |
| int ret; |
| bool en; |
| |
| ret = kstrtobool(buf, &en); |
| if (ret) |
| return ret; |
| |
| ret = regmap_update_bits(st->regmap, private, BIT(chan->channel), |
| en << chan->channel); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static ssize_t ltc2688_dither_freq_avail(const struct ltc2688_state *st, |
| const struct ltc2688_chan *chan, |
| char *buf) |
| { |
| int sz = 0; |
| u32 f; |
| |
| for (f = 0; f < ARRAY_SIZE(chan->dither_frequency); f++) |
| sz += sysfs_emit_at(buf, sz, "%ld ", chan->dither_frequency[f]); |
| |
| buf[sz - 1] = '\n'; |
| |
| return sz; |
| } |
| |
| static ssize_t ltc2688_dither_freq_get(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| char *buf) |
| { |
| const struct ltc2688_state *st = iio_priv(indio_dev); |
| const struct ltc2688_chan *c = &st->channels[chan->channel]; |
| u32 reg, freq; |
| int ret; |
| |
| if (private == LTC2688_DITHER_FREQ_AVAIL) |
| return ltc2688_dither_freq_avail(st, c, buf); |
| |
| ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(chan->channel), |
| ®); |
| if (ret) |
| return ret; |
| |
| freq = FIELD_GET(LTC2688_CH_DIT_PER_MSK, reg); |
| if (freq >= ARRAY_SIZE(c->dither_frequency)) |
| return -EIO; |
| |
| return sysfs_emit(buf, "%ld\n", c->dither_frequency[freq]); |
| } |
| |
| static ssize_t ltc2688_dither_freq_set(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| const struct ltc2688_state *st = iio_priv(indio_dev); |
| const struct ltc2688_chan *c = &st->channels[chan->channel]; |
| long val; |
| u32 freq; |
| int ret; |
| |
| if (private == LTC2688_DITHER_FREQ_AVAIL) |
| return -EINVAL; |
| |
| ret = kstrtol(buf, 10, &val); |
| if (ret) |
| return ret; |
| |
| for (freq = 0; freq < ARRAY_SIZE(c->dither_frequency); freq++) { |
| if (val == c->dither_frequency[freq]) |
| break; |
| } |
| |
| if (freq == ARRAY_SIZE(c->dither_frequency)) |
| return -EINVAL; |
| |
| ret = regmap_update_bits(st->regmap, |
| LTC2688_CMD_CH_SETTING(chan->channel), |
| LTC2688_CH_DIT_PER_MSK, |
| FIELD_PREP(LTC2688_CH_DIT_PER_MSK, freq)); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static ssize_t ltc2688_dac_input_read(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| char *buf) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| int ret; |
| u32 val; |
| |
| if (private == LTC2688_INPUT_B_AVAIL) |
| return sysfs_emit(buf, "[%u %u %u]\n", ltc2688_raw_range[0], |
| ltc2688_raw_range[1], |
| ltc2688_raw_range[2] / 4); |
| |
| if (private == LTC2688_DITHER_OFF) |
| return sysfs_emit(buf, "0\n"); |
| |
| ret = ltc2688_dac_code_read(st, chan->channel, private, &val); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%u\n", val); |
| } |
| |
| static ssize_t ltc2688_dac_input_write(struct iio_dev *indio_dev, |
| uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| int ret; |
| u16 val; |
| |
| if (private == LTC2688_INPUT_B_AVAIL || private == LTC2688_DITHER_OFF) |
| return -EINVAL; |
| |
| ret = kstrtou16(buf, 10, &val); |
| if (ret) |
| return ret; |
| |
| ret = ltc2688_dac_code_write(st, chan->channel, private, val); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static int ltc2688_get_dither_phase(struct iio_dev *dev, |
| const struct iio_chan_spec *chan) |
| { |
| struct ltc2688_state *st = iio_priv(dev); |
| int ret, regval; |
| |
| ret = regmap_read(st->regmap, LTC2688_CMD_CH_SETTING(chan->channel), |
| ®val); |
| if (ret) |
| return ret; |
| |
| return FIELD_GET(LTC2688_CH_DIT_PH_MSK, regval); |
| } |
| |
| static int ltc2688_set_dither_phase(struct iio_dev *dev, |
| const struct iio_chan_spec *chan, |
| unsigned int phase) |
| { |
| struct ltc2688_state *st = iio_priv(dev); |
| |
| return regmap_update_bits(st->regmap, |
| LTC2688_CMD_CH_SETTING(chan->channel), |
| LTC2688_CH_DIT_PH_MSK, |
| FIELD_PREP(LTC2688_CH_DIT_PH_MSK, phase)); |
| } |
| |
| static int ltc2688_reg_access(struct iio_dev *indio_dev, |
| unsigned int reg, |
| unsigned int writeval, |
| unsigned int *readval) |
| { |
| struct ltc2688_state *st = iio_priv(indio_dev); |
| |
| if (readval) |
| return regmap_read(st->regmap, reg, readval); |
| |
| return regmap_write(st->regmap, reg, writeval); |
| } |
| |
| static const char * const ltc2688_dither_phase[] = { |
| "0", "1.5708", "3.14159", "4.71239", |
| }; |
| |
| static const struct iio_enum ltc2688_dither_phase_enum = { |
| .items = ltc2688_dither_phase, |
| .num_items = ARRAY_SIZE(ltc2688_dither_phase), |
| .set = ltc2688_set_dither_phase, |
| .get = ltc2688_get_dither_phase, |
| }; |
| |
| #define LTC2688_CHAN_EXT_INFO(_name, _what, _shared, _read, _write) { \ |
| .name = _name, \ |
| .read = (_read), \ |
| .write = (_write), \ |
| .private = (_what), \ |
| .shared = (_shared), \ |
| } |
| |
| /* |
| * For toggle mode we only expose the symbol attr (sw_toggle) in case a TGPx is |
| * not provided in dts. |
| */ |
| static const struct iio_chan_spec_ext_info ltc2688_toggle_sym_ext_info[] = { |
| LTC2688_CHAN_EXT_INFO("raw0", LTC2688_INPUT_A, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("raw1", LTC2688_INPUT_B, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("toggle_en", LTC2688_CMD_TOGGLE_DITHER_EN, |
| IIO_SEPARATE, ltc2688_reg_bool_get, |
| ltc2688_dither_toggle_set), |
| LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, |
| ltc2688_reg_bool_get, ltc2688_reg_bool_set), |
| LTC2688_CHAN_EXT_INFO("symbol", LTC2688_CMD_SW_TOGGLE, IIO_SEPARATE, |
| ltc2688_reg_bool_get, ltc2688_reg_bool_set), |
| {} |
| }; |
| |
| static const struct iio_chan_spec_ext_info ltc2688_toggle_ext_info[] = { |
| LTC2688_CHAN_EXT_INFO("raw0", LTC2688_INPUT_A, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("raw1", LTC2688_INPUT_B, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("toggle_en", LTC2688_CMD_TOGGLE_DITHER_EN, |
| IIO_SEPARATE, ltc2688_reg_bool_get, |
| ltc2688_dither_toggle_set), |
| LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, |
| ltc2688_reg_bool_get, ltc2688_reg_bool_set), |
| {} |
| }; |
| |
| static struct iio_chan_spec_ext_info ltc2688_dither_ext_info[] = { |
| LTC2688_CHAN_EXT_INFO("dither_raw", LTC2688_INPUT_B, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("dither_raw_available", LTC2688_INPUT_B_AVAIL, |
| IIO_SEPARATE, ltc2688_dac_input_read, |
| ltc2688_dac_input_write), |
| LTC2688_CHAN_EXT_INFO("dither_offset", LTC2688_DITHER_OFF, IIO_SEPARATE, |
| ltc2688_dac_input_read, ltc2688_dac_input_write), |
| /* |
| * Not IIO_ENUM because the available freq needs to be computed at |
| * probe. We could still use it, but it didn't felt much right. |
| */ |
| LTC2688_CHAN_EXT_INFO("dither_frequency", 0, IIO_SEPARATE, |
| ltc2688_dither_freq_get, ltc2688_dither_freq_set), |
| LTC2688_CHAN_EXT_INFO("dither_frequency_available", |
| LTC2688_DITHER_FREQ_AVAIL, IIO_SEPARATE, |
| ltc2688_dither_freq_get, ltc2688_dither_freq_set), |
| IIO_ENUM("dither_phase", IIO_SEPARATE, <c2688_dither_phase_enum), |
| IIO_ENUM_AVAILABLE("dither_phase", IIO_SEPARATE, |
| <c2688_dither_phase_enum), |
| LTC2688_CHAN_EXT_INFO("dither_en", LTC2688_CMD_TOGGLE_DITHER_EN, |
| IIO_SEPARATE, ltc2688_reg_bool_get, |
| ltc2688_dither_toggle_set), |
| LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, |
| ltc2688_reg_bool_get, ltc2688_reg_bool_set), |
| {} |
| }; |
| |
| static const struct iio_chan_spec_ext_info ltc2688_ext_info[] = { |
| LTC2688_CHAN_EXT_INFO("powerdown", LTC2688_CMD_POWERDOWN, IIO_SEPARATE, |
| ltc2688_reg_bool_get, ltc2688_reg_bool_set), |
| {} |
| }; |
| |
| #define LTC2688_CHANNEL(_chan) { \ |
| .type = IIO_VOLTAGE, \ |
| .indexed = 1, \ |
| .output = 1, \ |
| .channel = (_chan), \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_CALIBSCALE) | \ |
| BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_OFFSET) | \ |
| BIT(IIO_CHAN_INFO_CALIBBIAS) | BIT(IIO_CHAN_INFO_RAW), \ |
| .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \ |
| .ext_info = ltc2688_ext_info, \ |
| } |
| |
| static const struct iio_chan_spec ltc2688_channels[] = { |
| LTC2688_CHANNEL(0), |
| LTC2688_CHANNEL(1), |
| LTC2688_CHANNEL(2), |
| LTC2688_CHANNEL(3), |
| LTC2688_CHANNEL(4), |
| LTC2688_CHANNEL(5), |
| LTC2688_CHANNEL(6), |
| LTC2688_CHANNEL(7), |
| LTC2688_CHANNEL(8), |
| LTC2688_CHANNEL(9), |
| LTC2688_CHANNEL(10), |
| LTC2688_CHANNEL(11), |
| LTC2688_CHANNEL(12), |
| LTC2688_CHANNEL(13), |
| LTC2688_CHANNEL(14), |
| LTC2688_CHANNEL(15), |
| }; |
| |
| static void ltc2688_clk_disable(void *clk) |
| { |
| clk_disable_unprepare(clk); |
| } |
| |
| static const int ltc2688_period[LTC2688_DITHER_FREQ_AVAIL_N] = { |
| 4, 8, 16, 32, 64, |
| }; |
| |
| static int ltc2688_tgp_clk_setup(struct ltc2688_state *st, |
| struct ltc2688_chan *chan, |
| struct fwnode_handle *node, int tgp) |
| { |
| struct device *dev = &st->spi->dev; |
| unsigned long rate; |
| struct clk *clk; |
| int ret, f; |
| |
| clk = devm_get_clk_from_child(dev, to_of_node(node), NULL); |
| if (IS_ERR(clk)) |
| return dev_err_probe(dev, PTR_ERR(clk), "failed to get tgp clk.\n"); |
| |
| ret = clk_prepare_enable(clk); |
| if (ret) |
| return dev_err_probe(dev, ret, "failed to enable tgp clk.\n"); |
| |
| ret = devm_add_action_or_reset(dev, ltc2688_clk_disable, clk); |
| if (ret) |
| return ret; |
| |
| if (chan->toggle_chan) |
| return 0; |
| |
| /* calculate available dither frequencies */ |
| rate = clk_get_rate(clk); |
| for (f = 0; f < ARRAY_SIZE(chan->dither_frequency); f++) |
| chan->dither_frequency[f] = DIV_ROUND_CLOSEST(rate, ltc2688_period[f]); |
| |
| return 0; |
| } |
| |
| static int ltc2688_span_lookup(const struct ltc2688_state *st, int min, int max) |
| { |
| u32 span; |
| |
| for (span = 0; span < ARRAY_SIZE(ltc2688_span_helper); span++) { |
| if (min == ltc2688_span_helper[span][0] && |
| max == ltc2688_span_helper[span][1]) |
| return span; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int ltc2688_channel_config(struct ltc2688_state *st) |
| { |
| struct device *dev = &st->spi->dev; |
| struct fwnode_handle *child; |
| u32 reg, clk_input, val, tmp[2]; |
| int ret, span; |
| |
| device_for_each_child_node(dev, child) { |
| struct ltc2688_chan *chan; |
| |
| ret = fwnode_property_read_u32(child, "reg", ®); |
| if (ret) { |
| fwnode_handle_put(child); |
| return dev_err_probe(dev, ret, |
| "Failed to get reg property\n"); |
| } |
| |
| if (reg >= LTC2688_DAC_CHANNELS) { |
| fwnode_handle_put(child); |
| return dev_err_probe(dev, -EINVAL, |
| "reg bigger than: %d\n", |
| LTC2688_DAC_CHANNELS); |
| } |
| |
| val = 0; |
| chan = &st->channels[reg]; |
| if (fwnode_property_read_bool(child, "adi,toggle-mode")) { |
| chan->toggle_chan = true; |
| /* assume sw toggle ABI */ |
| st->iio_chan[reg].ext_info = ltc2688_toggle_sym_ext_info; |
| /* |
| * Clear IIO_CHAN_INFO_RAW bit as toggle channels expose |
| * out_voltage_raw{0|1} files. |
| */ |
| __clear_bit(IIO_CHAN_INFO_RAW, |
| &st->iio_chan[reg].info_mask_separate); |
| } |
| |
| ret = fwnode_property_read_u32_array(child, "adi,output-range-microvolt", |
| tmp, ARRAY_SIZE(tmp)); |
| if (!ret) { |
| span = ltc2688_span_lookup(st, (int)tmp[0] / 1000, |
| tmp[1] / 1000); |
| if (span < 0) { |
| fwnode_handle_put(child); |
| return dev_err_probe(dev, -EINVAL, |
| "output range not valid:[%d %d]\n", |
| tmp[0], tmp[1]); |
| } |
| |
| val |= FIELD_PREP(LTC2688_CH_SPAN_MSK, span); |
| } |
| |
| ret = fwnode_property_read_u32(child, "adi,toggle-dither-input", |
| &clk_input); |
| if (!ret) { |
| if (clk_input >= LTC2688_CH_TGP_MAX) { |
| fwnode_handle_put(child); |
| return dev_err_probe(dev, -EINVAL, |
| "toggle-dither-input inv value(%d)\n", |
| clk_input); |
| } |
| |
| ret = ltc2688_tgp_clk_setup(st, chan, child, clk_input); |
| if (ret) { |
| fwnode_handle_put(child); |
| return ret; |
| } |
| |
| /* |
| * 0 means software toggle which is the default mode. |
| * Hence the +1. |
| */ |
| val |= FIELD_PREP(LTC2688_CH_TD_SEL_MSK, clk_input + 1); |
| |
| /* |
| * If a TGPx is given, we automatically assume a dither |
| * capable channel (unless toggle is already enabled). |
| * On top of this we just set here the dither bit in the |
| * channel settings. It won't have any effect until the |
| * global toggle/dither bit is enabled. |
| */ |
| if (!chan->toggle_chan) { |
| val |= FIELD_PREP(LTC2688_CH_MODE_MSK, 1); |
| st->iio_chan[reg].ext_info = ltc2688_dither_ext_info; |
| } else { |
| /* wait, no sw toggle after all */ |
| st->iio_chan[reg].ext_info = ltc2688_toggle_ext_info; |
| } |
| } |
| |
| if (fwnode_property_read_bool(child, "adi,overrange")) { |
| chan->overrange = true; |
| val |= LTC2688_CH_OVERRANGE_MSK; |
| } |
| |
| if (!val) |
| continue; |
| |
| ret = regmap_write(st->regmap, LTC2688_CMD_CH_SETTING(reg), |
| val); |
| if (ret) { |
| fwnode_handle_put(child); |
| return dev_err_probe(dev, -EINVAL, |
| "failed to set chan settings\n"); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int ltc2688_setup(struct ltc2688_state *st, struct regulator *vref) |
| { |
| struct device *dev = &st->spi->dev; |
| struct gpio_desc *gpio; |
| int ret; |
| |
| /* |
| * If we have a reset pin, use that to reset the board, If not, use |
| * the reset bit. |
| */ |
| gpio = devm_gpiod_get_optional(dev, "clr", GPIOD_OUT_HIGH); |
| if (IS_ERR(gpio)) |
| return dev_err_probe(dev, PTR_ERR(gpio), "Failed to get reset gpio"); |
| if (gpio) { |
| usleep_range(1000, 1200); |
| /* bring device out of reset */ |
| gpiod_set_value_cansleep(gpio, 0); |
| } else { |
| ret = regmap_update_bits(st->regmap, LTC2688_CMD_CONFIG, |
| LTC2688_CONFIG_RST, |
| LTC2688_CONFIG_RST); |
| if (ret) |
| return ret; |
| } |
| |
| usleep_range(10000, 12000); |
| |
| /* |
| * Duplicate the default channel configuration as it can change during |
| * @ltc2688_channel_config() |
| */ |
| st->iio_chan = devm_kmemdup(dev, ltc2688_channels, |
| sizeof(ltc2688_channels), GFP_KERNEL); |
| if (!st->iio_chan) |
| return -ENOMEM; |
| |
| ret = ltc2688_channel_config(st); |
| if (ret) |
| return ret; |
| |
| if (!vref) |
| return 0; |
| |
| return regmap_set_bits(st->regmap, LTC2688_CMD_CONFIG, |
| LTC2688_CONFIG_EXT_REF); |
| } |
| |
| static void ltc2688_disable_regulators(void *data) |
| { |
| struct ltc2688_state *st = data; |
| |
| regulator_bulk_disable(ARRAY_SIZE(st->regulators), st->regulators); |
| } |
| |
| static void ltc2688_disable_regulator(void *regulator) |
| { |
| regulator_disable(regulator); |
| } |
| |
| static bool ltc2688_reg_readable(struct device *dev, unsigned int reg) |
| { |
| switch (reg) { |
| case LTC2688_CMD_CH_CODE(0) ... LTC2688_CMD_CH_GAIN(15): |
| return true; |
| case LTC2688_CMD_CONFIG ... LTC2688_CMD_THERMAL_STAT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| static bool ltc2688_reg_writable(struct device *dev, unsigned int reg) |
| { |
| /* |
| * There's a jump from 0x76 to 0x78 in the write codes and the thermal |
| * status code is 0x77 (which is read only) so that we need to check |
| * that special condition. |
| */ |
| if (reg <= LTC2688_CMD_UPDATE_ALL && reg != LTC2688_CMD_THERMAL_STAT) |
| return true; |
| |
| return false; |
| } |
| |
| static struct regmap_bus ltc2688_regmap_bus = { |
| .read = ltc2688_spi_read, |
| .write = ltc2688_spi_write, |
| .read_flag_mask = LTC2688_READ_OPERATION, |
| .reg_format_endian_default = REGMAP_ENDIAN_BIG, |
| .val_format_endian_default = REGMAP_ENDIAN_BIG, |
| }; |
| |
| static const struct regmap_config ltc2688_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 16, |
| .readable_reg = ltc2688_reg_readable, |
| .writeable_reg = ltc2688_reg_writable, |
| /* ignoring the no op command */ |
| .max_register = LTC2688_CMD_UPDATE_ALL, |
| }; |
| |
| static const struct iio_info ltc2688_info = { |
| .write_raw = ltc2688_write_raw, |
| .read_raw = ltc2688_read_raw, |
| .read_avail = ltc2688_read_avail, |
| .debugfs_reg_access = ltc2688_reg_access, |
| }; |
| |
| static int ltc2688_probe(struct spi_device *spi) |
| { |
| struct ltc2688_state *st; |
| struct iio_dev *indio_dev; |
| struct regulator *vref_reg; |
| struct device *dev = &spi->dev; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| st = iio_priv(indio_dev); |
| st->spi = spi; |
| |
| /* Just write this once. No need to do it in every regmap read. */ |
| st->tx_data[3] = LTC2688_CMD_NOOP; |
| mutex_init(&st->lock); |
| |
| st->regmap = devm_regmap_init(dev, <c2688_regmap_bus, st, |
| <c2688_regmap_config); |
| if (IS_ERR(st->regmap)) |
| return dev_err_probe(dev, PTR_ERR(st->regmap), |
| "Failed to init regmap"); |
| |
| st->regulators[0].supply = "vcc"; |
| st->regulators[1].supply = "iovcc"; |
| ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(st->regulators), |
| st->regulators); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to get regulators\n"); |
| |
| ret = regulator_bulk_enable(ARRAY_SIZE(st->regulators), st->regulators); |
| if (ret) |
| return dev_err_probe(dev, ret, "Failed to enable regulators\n"); |
| |
| ret = devm_add_action_or_reset(dev, ltc2688_disable_regulators, st); |
| if (ret) |
| return ret; |
| |
| vref_reg = devm_regulator_get_optional(dev, "vref"); |
| if (IS_ERR(vref_reg)) { |
| if (PTR_ERR(vref_reg) != -ENODEV) |
| return dev_err_probe(dev, PTR_ERR(vref_reg), |
| "Failed to get vref regulator"); |
| |
| vref_reg = NULL; |
| /* internal reference */ |
| st->vref = 4096; |
| } else { |
| ret = regulator_enable(vref_reg); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "Failed to enable vref regulators\n"); |
| |
| ret = devm_add_action_or_reset(dev, ltc2688_disable_regulator, |
| vref_reg); |
| if (ret) |
| return ret; |
| |
| ret = regulator_get_voltage(vref_reg); |
| if (ret < 0) |
| return dev_err_probe(dev, ret, "Failed to get vref\n"); |
| |
| st->vref = ret / 1000; |
| } |
| |
| ret = ltc2688_setup(st, vref_reg); |
| if (ret) |
| return ret; |
| |
| indio_dev->name = "ltc2688"; |
| indio_dev->info = <c2688_info; |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->channels = st->iio_chan; |
| indio_dev->num_channels = ARRAY_SIZE(ltc2688_channels); |
| |
| return devm_iio_device_register(dev, indio_dev); |
| } |
| |
| static const struct of_device_id ltc2688_of_id[] = { |
| { .compatible = "adi,ltc2688" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, ltc2688_of_id); |
| |
| static const struct spi_device_id ltc2688_id[] = { |
| { "ltc2688" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(spi, ltc2688_id); |
| |
| static struct spi_driver ltc2688_driver = { |
| .driver = { |
| .name = "ltc2688", |
| .of_match_table = ltc2688_of_id, |
| }, |
| .probe = ltc2688_probe, |
| .id_table = ltc2688_id, |
| }; |
| module_spi_driver(ltc2688_driver); |
| |
| MODULE_AUTHOR("Nuno Sá <nuno.sa@analog.com>"); |
| MODULE_DESCRIPTION("Analog Devices LTC2688 DAC"); |
| MODULE_LICENSE("GPL"); |