| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * AD8460 Waveform generator DAC Driver |
| * |
| * Copyright (C) 2024 Analog Devices, Inc. |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/cleanup.h> |
| #include <linux/clk.h> |
| #include <linux/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/dmaengine.h> |
| #include <linux/gpio/consumer.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/spi/spi.h> |
| |
| #include <linux/iio/buffer.h> |
| #include <linux/iio/buffer-dma.h> |
| #include <linux/iio/buffer-dmaengine.h> |
| #include <linux/iio/consumer.h> |
| #include <linux/iio/events.h> |
| #include <linux/iio/iio.h> |
| |
| #define AD8460_CTRL_REG(x) (x) |
| #define AD8460_HVDAC_DATA_WORD(x) (0x60 + (2 * (x))) |
| |
| #define AD8460_HV_RESET_MSK BIT(7) |
| #define AD8460_HV_SLEEP_MSK BIT(4) |
| #define AD8460_WAVE_GEN_MODE_MSK BIT(0) |
| |
| #define AD8460_HVDAC_SLEEP_MSK BIT(3) |
| |
| #define AD8460_FAULT_ARM_MSK BIT(7) |
| #define AD8460_FAULT_LIMIT_MSK GENMASK(6, 0) |
| |
| #define AD8460_APG_MODE_ENABLE_MSK BIT(5) |
| #define AD8460_PATTERN_DEPTH_MSK GENMASK(3, 0) |
| |
| #define AD8460_QUIESCENT_CURRENT_MSK GENMASK(7, 0) |
| |
| #define AD8460_SHUTDOWN_FLAG_MSK BIT(7) |
| |
| #define AD8460_DATA_BYTE_LOW_MSK GENMASK(7, 0) |
| #define AD8460_DATA_BYTE_HIGH_MSK GENMASK(5, 0) |
| #define AD8460_DATA_BYTE_FULL_MSK GENMASK(13, 0) |
| |
| #define AD8460_DEFAULT_FAULT_PROTECT 0x00 |
| #define AD8460_DATA_BYTE_WORD_LENGTH 2 |
| #define AD8460_NUM_DATA_WORDS 16 |
| #define AD8460_NOMINAL_VOLTAGE_SPAN 80 |
| #define AD8460_MIN_EXT_RESISTOR_OHMS 2000 |
| #define AD8460_MAX_EXT_RESISTOR_OHMS 20000 |
| #define AD8460_MIN_VREFIO_UV 120000 |
| #define AD8460_MAX_VREFIO_UV 1200000 |
| #define AD8460_ABS_MAX_OVERVOLTAGE_UV 55000000 |
| #define AD8460_ABS_MAX_OVERCURRENT_UA 1000000 |
| #define AD8460_MAX_OVERTEMPERATURE_MC 150000 |
| #define AD8460_MIN_OVERTEMPERATURE_MC 20000 |
| #define AD8460_CURRENT_LIMIT_CONV(x) ((x) / 15625) |
| #define AD8460_VOLTAGE_LIMIT_CONV(x) ((x) / 1953000) |
| #define AD8460_TEMP_LIMIT_CONV(x) (((x) + 266640) / 6510) |
| |
| enum ad8460_fault_type { |
| AD8460_OVERCURRENT_SRC, |
| AD8460_OVERCURRENT_SNK, |
| AD8460_OVERVOLTAGE_POS, |
| AD8460_OVERVOLTAGE_NEG, |
| AD8460_OVERTEMPERATURE, |
| }; |
| |
| struct ad8460_state { |
| struct spi_device *spi; |
| struct regmap *regmap; |
| struct iio_channel *tmp_adc_channel; |
| struct clk *sync_clk; |
| /* lock to protect against multiple access to the device and shared data */ |
| struct mutex lock; |
| int refio_1p2v_mv; |
| u32 ext_resistor_ohms; |
| /* |
| * DMA (thus cache coherency maintenance) requires the |
| * transfer buffers to live in their own cache lines. |
| */ |
| __le16 spi_tx_buf __aligned(IIO_DMA_MINALIGN); |
| }; |
| |
| static int ad8460_hv_reset(struct ad8460_state *state) |
| { |
| int ret; |
| |
| ret = regmap_set_bits(state->regmap, AD8460_CTRL_REG(0x00), |
| AD8460_HV_RESET_MSK); |
| if (ret) |
| return ret; |
| |
| fsleep(20); |
| |
| return regmap_clear_bits(state->regmap, AD8460_CTRL_REG(0x00), |
| AD8460_HV_RESET_MSK); |
| } |
| |
| static int ad8460_reset(const struct ad8460_state *state) |
| { |
| struct device *dev = &state->spi->dev; |
| struct gpio_desc *reset; |
| |
| reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); |
| if (IS_ERR(reset)) |
| return dev_err_probe(dev, PTR_ERR(reset), |
| "Failed to get reset gpio"); |
| if (reset) { |
| /* minimum duration of 10ns */ |
| ndelay(10); |
| gpiod_set_value_cansleep(reset, 1); |
| return 0; |
| } |
| |
| /* bring all registers to their default state */ |
| return regmap_write(state->regmap, AD8460_CTRL_REG(0x03), 1); |
| } |
| |
| static int ad8460_enable_apg_mode(struct ad8460_state *state, int val) |
| { |
| int ret; |
| |
| ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x02), |
| AD8460_APG_MODE_ENABLE_MSK, |
| FIELD_PREP(AD8460_APG_MODE_ENABLE_MSK, val)); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x00), |
| AD8460_WAVE_GEN_MODE_MSK, |
| FIELD_PREP(AD8460_WAVE_GEN_MODE_MSK, val)); |
| } |
| |
| static int ad8460_read_shutdown_flag(struct ad8460_state *state, u64 *flag) |
| { |
| int ret, val; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x0E), &val); |
| if (ret) |
| return ret; |
| |
| *flag = FIELD_GET(AD8460_SHUTDOWN_FLAG_MSK, val); |
| return 0; |
| } |
| |
| static int ad8460_get_hvdac_word(struct ad8460_state *state, int index, int *val) |
| { |
| int ret; |
| |
| ret = regmap_bulk_read(state->regmap, AD8460_HVDAC_DATA_WORD(index), |
| &state->spi_tx_buf, AD8460_DATA_BYTE_WORD_LENGTH); |
| if (ret) |
| return ret; |
| |
| *val = le16_to_cpu(state->spi_tx_buf); |
| |
| return ret; |
| } |
| |
| static int ad8460_set_hvdac_word(struct ad8460_state *state, int index, int val) |
| { |
| state->spi_tx_buf = cpu_to_le16(FIELD_PREP(AD8460_DATA_BYTE_FULL_MSK, val)); |
| |
| return regmap_bulk_write(state->regmap, AD8460_HVDAC_DATA_WORD(index), |
| &state->spi_tx_buf, AD8460_DATA_BYTE_WORD_LENGTH); |
| } |
| |
| static ssize_t ad8460_dac_input_read(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, char *buf) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| unsigned int reg; |
| int ret; |
| |
| ret = ad8460_get_hvdac_word(state, private, ®); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%u\n", reg); |
| } |
| |
| static ssize_t ad8460_dac_input_write(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| unsigned int reg; |
| int ret; |
| |
| ret = kstrtou32(buf, 10, ®); |
| if (ret) |
| return ret; |
| |
| guard(mutex)(&state->lock); |
| |
| return ad8460_set_hvdac_word(state, private, reg); |
| } |
| |
| static ssize_t ad8460_read_symbol(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, char *buf) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| unsigned int reg; |
| int ret; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x02), ®); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%lu\n", FIELD_GET(AD8460_PATTERN_DEPTH_MSK, reg)); |
| } |
| |
| static ssize_t ad8460_write_symbol(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| uint16_t sym; |
| int ret; |
| |
| ret = kstrtou16(buf, 10, &sym); |
| if (ret) |
| return ret; |
| |
| guard(mutex)(&state->lock); |
| |
| return regmap_update_bits(state->regmap, |
| AD8460_CTRL_REG(0x02), |
| AD8460_PATTERN_DEPTH_MSK, |
| FIELD_PREP(AD8460_PATTERN_DEPTH_MSK, sym)); |
| } |
| |
| static ssize_t ad8460_read_toggle_en(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, char *buf) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| unsigned int reg; |
| int ret; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x02), ®); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%ld\n", FIELD_GET(AD8460_APG_MODE_ENABLE_MSK, reg)); |
| } |
| |
| static ssize_t ad8460_write_toggle_en(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| bool toggle_en; |
| int ret; |
| |
| ret = kstrtobool(buf, &toggle_en); |
| if (ret) |
| return ret; |
| |
| if (!iio_device_claim_direct(indio_dev)) |
| return -EBUSY; |
| |
| ret = ad8460_enable_apg_mode(state, toggle_en); |
| iio_device_release_direct(indio_dev); |
| return ret; |
| } |
| |
| static ssize_t ad8460_read_powerdown(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, char *buf) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| unsigned int reg; |
| int ret; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x01), ®); |
| if (ret) |
| return ret; |
| |
| return sysfs_emit(buf, "%ld\n", FIELD_GET(AD8460_HVDAC_SLEEP_MSK, reg)); |
| } |
| |
| static ssize_t ad8460_write_powerdown(struct iio_dev *indio_dev, uintptr_t private, |
| const struct iio_chan_spec *chan, |
| const char *buf, size_t len) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| bool pwr_down; |
| u64 sdn_flag; |
| int ret; |
| |
| ret = kstrtobool(buf, &pwr_down); |
| if (ret) |
| return ret; |
| |
| guard(mutex)(&state->lock); |
| |
| /* |
| * If powerdown is set, HVDAC is enabled and the HV driver is |
| * enabled via HV_RESET in case it is in shutdown mode, |
| * If powerdown is cleared, HVDAC is set to shutdown state |
| * as well as the HV driver. Quiescent current decreases and ouput is |
| * floating (high impedance). |
| */ |
| |
| ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x01), |
| AD8460_HVDAC_SLEEP_MSK, |
| FIELD_PREP(AD8460_HVDAC_SLEEP_MSK, pwr_down)); |
| if (ret) |
| return ret; |
| |
| if (!pwr_down) { |
| ret = ad8460_read_shutdown_flag(state, &sdn_flag); |
| if (ret) |
| return ret; |
| |
| if (sdn_flag) { |
| ret = ad8460_hv_reset(state); |
| if (ret) |
| return ret; |
| } |
| } |
| |
| ret = regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x00), |
| AD8460_HV_SLEEP_MSK, |
| FIELD_PREP(AD8460_HV_SLEEP_MSK, !pwr_down)); |
| if (ret) |
| return ret; |
| |
| return len; |
| } |
| |
| static const char * const ad8460_powerdown_modes[] = { |
| "three_state", |
| }; |
| |
| static int ad8460_get_powerdown_mode(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan) |
| { |
| return 0; |
| } |
| |
| static int ad8460_set_powerdown_mode(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| unsigned int type) |
| { |
| return 0; |
| } |
| |
| static int ad8460_set_sample(struct ad8460_state *state, int val) |
| { |
| int ret; |
| |
| ret = ad8460_enable_apg_mode(state, 1); |
| if (ret) |
| return ret; |
| |
| guard(mutex)(&state->lock); |
| ret = ad8460_set_hvdac_word(state, 0, val); |
| if (ret) |
| return ret; |
| |
| return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x02), |
| AD8460_PATTERN_DEPTH_MSK, |
| FIELD_PREP(AD8460_PATTERN_DEPTH_MSK, 0)); |
| } |
| |
| static int ad8460_set_fault_threshold(struct ad8460_state *state, |
| enum ad8460_fault_type fault, |
| unsigned int threshold) |
| { |
| return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x08 + fault), |
| AD8460_FAULT_LIMIT_MSK, |
| FIELD_PREP(AD8460_FAULT_LIMIT_MSK, threshold)); |
| } |
| |
| static int ad8460_get_fault_threshold(struct ad8460_state *state, |
| enum ad8460_fault_type fault, |
| unsigned int *threshold) |
| { |
| unsigned int val; |
| int ret; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x08 + fault), &val); |
| if (ret) |
| return ret; |
| |
| *threshold = FIELD_GET(AD8460_FAULT_LIMIT_MSK, val); |
| |
| return ret; |
| } |
| |
| static int ad8460_set_fault_threshold_en(struct ad8460_state *state, |
| enum ad8460_fault_type fault, bool en) |
| { |
| return regmap_update_bits(state->regmap, AD8460_CTRL_REG(0x08 + fault), |
| AD8460_FAULT_ARM_MSK, |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, en)); |
| } |
| |
| static int ad8460_get_fault_threshold_en(struct ad8460_state *state, |
| enum ad8460_fault_type fault, bool *en) |
| { |
| unsigned int val; |
| int ret; |
| |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x08 + fault), &val); |
| if (ret) |
| return ret; |
| |
| *en = FIELD_GET(AD8460_FAULT_ARM_MSK, val); |
| |
| return 0; |
| } |
| |
| static int ad8460_write_raw(struct iio_dev *indio_dev, |
| struct iio_chan_spec const *chan, int val, int val2, |
| long mask) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| int ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| switch (chan->type) { |
| case IIO_VOLTAGE: |
| if (!iio_device_claim_direct(indio_dev)) |
| return -EBUSY; |
| ret = ad8460_set_sample(state, val); |
| iio_device_release_direct(indio_dev); |
| return ret; |
| case IIO_CURRENT: |
| return regmap_write(state->regmap, AD8460_CTRL_REG(0x04), |
| FIELD_PREP(AD8460_QUIESCENT_CURRENT_MSK, val)); |
| default: |
| return -EINVAL; |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ad8460_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, |
| int *val, int *val2, long mask) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| int data, ret; |
| |
| switch (mask) { |
| case IIO_CHAN_INFO_RAW: |
| switch (chan->type) { |
| case IIO_VOLTAGE: |
| scoped_guard(mutex, &state->lock) { |
| ret = ad8460_get_hvdac_word(state, 0, &data); |
| if (ret) |
| return ret; |
| } |
| *val = data; |
| return IIO_VAL_INT; |
| case IIO_CURRENT: |
| ret = regmap_read(state->regmap, AD8460_CTRL_REG(0x04), |
| &data); |
| if (ret) |
| return ret; |
| *val = data; |
| return IIO_VAL_INT; |
| case IIO_TEMP: |
| ret = iio_read_channel_raw(state->tmp_adc_channel, &data); |
| if (ret) |
| return ret; |
| *val = data; |
| return IIO_VAL_INT; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CHAN_INFO_SAMP_FREQ: |
| *val = clk_get_rate(state->sync_clk); |
| return IIO_VAL_INT; |
| case IIO_CHAN_INFO_SCALE: |
| /* |
| * vCONV = vNOMINAL_SPAN * (DAC_CODE / 2**14) - 40V |
| * vMAX = vNOMINAL_SPAN * (2**14 / 2**14) - 40V |
| * vMIN = vNOMINAL_SPAN * (0 / 2**14) - 40V |
| * vADJ = vCONV * (2000 / rSET) * (vREF / 1.2) |
| * vSPAN = vADJ_MAX - vADJ_MIN |
| * See datasheet page 49, section FULL-SCALE REDUCTION |
| */ |
| *val = AD8460_NOMINAL_VOLTAGE_SPAN * 2000 * state->refio_1p2v_mv; |
| *val2 = state->ext_resistor_ohms * 1200; |
| return IIO_VAL_FRACTIONAL; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ad8460_select_fault_type(int chan_type, enum iio_event_direction dir) |
| { |
| switch (chan_type) { |
| case IIO_VOLTAGE: |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return AD8460_OVERVOLTAGE_POS; |
| case IIO_EV_DIR_FALLING: |
| return AD8460_OVERVOLTAGE_NEG; |
| default: |
| return -EINVAL; |
| } |
| case IIO_CURRENT: |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return AD8460_OVERCURRENT_SRC; |
| case IIO_EV_DIR_FALLING: |
| return AD8460_OVERCURRENT_SNK; |
| default: |
| return -EINVAL; |
| } |
| case IIO_TEMP: |
| switch (dir) { |
| case IIO_EV_DIR_RISING: |
| return AD8460_OVERTEMPERATURE; |
| default: |
| return -EINVAL; |
| } |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static int ad8460_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) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| int fault; |
| |
| if (type != IIO_EV_TYPE_THRESH) |
| return -EINVAL; |
| |
| if (info != IIO_EV_INFO_VALUE) |
| return -EINVAL; |
| |
| fault = ad8460_select_fault_type(chan->type, dir); |
| if (fault < 0) |
| return fault; |
| |
| return ad8460_set_fault_threshold(state, fault, val); |
| } |
| |
| static int ad8460_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) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| int fault; |
| |
| if (type != IIO_EV_TYPE_THRESH) |
| return -EINVAL; |
| |
| if (info != IIO_EV_INFO_VALUE) |
| return -EINVAL; |
| |
| fault = ad8460_select_fault_type(chan->type, dir); |
| if (fault < 0) |
| return fault; |
| |
| return ad8460_get_fault_threshold(state, fault, val); |
| } |
| |
| static int ad8460_write_event_config(struct iio_dev *indio_dev, |
| const struct iio_chan_spec *chan, |
| enum iio_event_type type, |
| enum iio_event_direction dir, bool val) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| int fault; |
| |
| if (type != IIO_EV_TYPE_THRESH) |
| return -EINVAL; |
| |
| fault = ad8460_select_fault_type(chan->type, dir); |
| if (fault < 0) |
| return fault; |
| |
| return ad8460_set_fault_threshold_en(state, fault, val); |
| } |
| |
| static int ad8460_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 ad8460_state *state = iio_priv(indio_dev); |
| int fault, ret; |
| bool en; |
| |
| if (type != IIO_EV_TYPE_THRESH) |
| return -EINVAL; |
| |
| fault = ad8460_select_fault_type(chan->type, dir); |
| if (fault < 0) |
| return fault; |
| |
| ret = ad8460_get_fault_threshold_en(state, fault, &en); |
| if (ret) |
| return ret; |
| |
| return en; |
| } |
| |
| static int ad8460_reg_access(struct iio_dev *indio_dev, unsigned int reg, |
| unsigned int writeval, unsigned int *readval) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| |
| if (readval) |
| return regmap_read(state->regmap, reg, readval); |
| |
| return regmap_write(state->regmap, reg, writeval); |
| } |
| |
| static int ad8460_buffer_preenable(struct iio_dev *indio_dev) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| |
| return ad8460_enable_apg_mode(state, 0); |
| } |
| |
| static int ad8460_buffer_postdisable(struct iio_dev *indio_dev) |
| { |
| struct ad8460_state *state = iio_priv(indio_dev); |
| |
| return ad8460_enable_apg_mode(state, 1); |
| } |
| |
| static const struct iio_buffer_setup_ops ad8460_buffer_setup_ops = { |
| .preenable = &ad8460_buffer_preenable, |
| .postdisable = &ad8460_buffer_postdisable, |
| }; |
| |
| static const struct iio_info ad8460_info = { |
| .read_raw = &ad8460_read_raw, |
| .write_raw = &ad8460_write_raw, |
| .write_event_value = &ad8460_write_event_value, |
| .read_event_value = &ad8460_read_event_value, |
| .write_event_config = &ad8460_write_event_config, |
| .read_event_config = &ad8460_read_event_config, |
| .debugfs_reg_access = &ad8460_reg_access, |
| }; |
| |
| static const struct iio_enum ad8460_powerdown_mode_enum = { |
| .items = ad8460_powerdown_modes, |
| .num_items = ARRAY_SIZE(ad8460_powerdown_modes), |
| .get = ad8460_get_powerdown_mode, |
| .set = ad8460_set_powerdown_mode, |
| }; |
| |
| #define AD8460_CHAN_EXT_INFO(_name, _what, _read, _write) { \ |
| .name = (_name), \ |
| .read = (_read), \ |
| .write = (_write), \ |
| .private = (_what), \ |
| .shared = IIO_SEPARATE, \ |
| } |
| |
| static const struct iio_chan_spec_ext_info ad8460_ext_info[] = { |
| AD8460_CHAN_EXT_INFO("raw0", 0, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw1", 1, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw2", 2, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw3", 3, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw4", 4, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw5", 5, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw6", 6, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw7", 7, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw8", 8, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw9", 9, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw10", 10, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw11", 11, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw12", 12, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw13", 13, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw14", 14, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("raw15", 15, ad8460_dac_input_read, |
| ad8460_dac_input_write), |
| AD8460_CHAN_EXT_INFO("toggle_en", 0, ad8460_read_toggle_en, |
| ad8460_write_toggle_en), |
| AD8460_CHAN_EXT_INFO("symbol", 0, ad8460_read_symbol, |
| ad8460_write_symbol), |
| AD8460_CHAN_EXT_INFO("powerdown", 0, ad8460_read_powerdown, |
| ad8460_write_powerdown), |
| IIO_ENUM("powerdown_mode", IIO_SEPARATE, &ad8460_powerdown_mode_enum), |
| IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, |
| &ad8460_powerdown_mode_enum), |
| { } |
| }; |
| |
| static const struct iio_event_spec ad8460_events[] = { |
| { |
| .type = IIO_EV_TYPE_THRESH, |
| .dir = IIO_EV_DIR_RISING, |
| .mask_separate = BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_ENABLE), |
| }, |
| { |
| .type = IIO_EV_TYPE_THRESH, |
| .dir = IIO_EV_DIR_FALLING, |
| .mask_separate = BIT(IIO_EV_INFO_VALUE) | |
| BIT(IIO_EV_INFO_ENABLE), |
| }, |
| }; |
| |
| #define AD8460_VOLTAGE_CHAN { \ |
| .type = IIO_VOLTAGE, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ |
| BIT(IIO_CHAN_INFO_RAW) | \ |
| BIT(IIO_CHAN_INFO_SCALE), \ |
| .output = 1, \ |
| .indexed = 1, \ |
| .channel = 0, \ |
| .scan_index = 0, \ |
| .scan_type = { \ |
| .sign = 'u', \ |
| .realbits = 14, \ |
| .storagebits = 16, \ |
| .endianness = IIO_CPU, \ |
| }, \ |
| .ext_info = ad8460_ext_info, \ |
| .event_spec = ad8460_events, \ |
| .num_event_specs = ARRAY_SIZE(ad8460_events), \ |
| } |
| |
| #define AD8460_CURRENT_CHAN { \ |
| .type = IIO_CURRENT, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .output = 1, \ |
| .indexed = 1, \ |
| .channel = 0, \ |
| .scan_index = -1, \ |
| .event_spec = ad8460_events, \ |
| .num_event_specs = ARRAY_SIZE(ad8460_events), \ |
| } |
| |
| #define AD8460_TEMP_CHAN { \ |
| .type = IIO_TEMP, \ |
| .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ |
| .indexed = 1, \ |
| .channel = 0, \ |
| .scan_index = -1, \ |
| .event_spec = ad8460_events, \ |
| .num_event_specs = 1, \ |
| } |
| |
| static const struct iio_chan_spec ad8460_channels[] = { |
| AD8460_VOLTAGE_CHAN, |
| AD8460_CURRENT_CHAN, |
| }; |
| |
| static const struct iio_chan_spec ad8460_channels_with_tmp_adc[] = { |
| AD8460_VOLTAGE_CHAN, |
| AD8460_CURRENT_CHAN, |
| AD8460_TEMP_CHAN, |
| }; |
| |
| static const struct regmap_config ad8460_regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .max_register = 0x7F, |
| }; |
| |
| static const char * const ad8460_supplies[] = { |
| "avdd_3p3v", "dvdd_3p3v", "vcc_5v", "hvcc", "hvee", "vref_5v" |
| }; |
| |
| static int ad8460_probe(struct spi_device *spi) |
| { |
| struct device *dev = &spi->dev; |
| struct ad8460_state *state; |
| struct iio_dev *indio_dev; |
| u32 tmp[2], temp; |
| int ret; |
| |
| indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state)); |
| if (!indio_dev) |
| return -ENOMEM; |
| |
| state = iio_priv(indio_dev); |
| |
| indio_dev->name = "ad8460"; |
| indio_dev->info = &ad8460_info; |
| |
| state->spi = spi; |
| |
| state->regmap = devm_regmap_init_spi(spi, &ad8460_regmap_config); |
| if (IS_ERR(state->regmap)) |
| return dev_err_probe(dev, PTR_ERR(state->regmap), |
| "Failed to initialize regmap"); |
| |
| ret = devm_mutex_init(dev, &state->lock); |
| if (ret) |
| return ret; |
| |
| state->sync_clk = devm_clk_get_enabled(dev, NULL); |
| if (IS_ERR(state->sync_clk)) |
| return dev_err_probe(dev, PTR_ERR(state->sync_clk), |
| "Failed to get sync clk\n"); |
| |
| state->tmp_adc_channel = devm_iio_channel_get(dev, "ad8460-tmp"); |
| if (IS_ERR(state->tmp_adc_channel)) { |
| if (PTR_ERR(state->tmp_adc_channel) == -EPROBE_DEFER) |
| return -EPROBE_DEFER; |
| indio_dev->channels = ad8460_channels; |
| indio_dev->num_channels = ARRAY_SIZE(ad8460_channels); |
| } else { |
| indio_dev->channels = ad8460_channels_with_tmp_adc; |
| indio_dev->num_channels = ARRAY_SIZE(ad8460_channels_with_tmp_adc); |
| } |
| |
| ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad8460_supplies), |
| ad8460_supplies); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "Failed to enable power supplies\n"); |
| |
| ret = devm_regulator_get_enable_read_voltage(dev, "refio_1p2v"); |
| if (ret < 0 && ret != -ENODEV) |
| return dev_err_probe(dev, ret, "Failed to get reference voltage\n"); |
| |
| state->refio_1p2v_mv = ret == -ENODEV ? 1200 : ret / 1000; |
| |
| if (!in_range(state->refio_1p2v_mv, AD8460_MIN_VREFIO_UV / 1000, |
| AD8460_MAX_VREFIO_UV / 1000)) |
| return dev_err_probe(dev, -EINVAL, |
| "Invalid ref voltage range(%u mV) [%u mV, %u mV]\n", |
| state->refio_1p2v_mv, |
| AD8460_MIN_VREFIO_UV / 1000, |
| AD8460_MAX_VREFIO_UV / 1000); |
| |
| ret = device_property_read_u32(dev, "adi,external-resistor-ohms", |
| &state->ext_resistor_ohms); |
| if (ret) |
| state->ext_resistor_ohms = 2000; |
| else if (!in_range(state->ext_resistor_ohms, AD8460_MIN_EXT_RESISTOR_OHMS, |
| AD8460_MAX_EXT_RESISTOR_OHMS)) |
| return dev_err_probe(dev, -EINVAL, |
| "Invalid resistor set range(%u) [%u, %u]\n", |
| state->ext_resistor_ohms, |
| AD8460_MIN_EXT_RESISTOR_OHMS, |
| AD8460_MAX_EXT_RESISTOR_OHMS); |
| |
| ret = device_property_read_u32_array(dev, "adi,range-microamp", |
| tmp, ARRAY_SIZE(tmp)); |
| if (!ret) { |
| if (in_range(tmp[1], 0, AD8460_ABS_MAX_OVERCURRENT_UA)) |
| regmap_write(state->regmap, AD8460_CTRL_REG(0x08), |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | |
| AD8460_CURRENT_LIMIT_CONV(tmp[1])); |
| |
| if (in_range(tmp[0], -AD8460_ABS_MAX_OVERCURRENT_UA, 0)) |
| regmap_write(state->regmap, AD8460_CTRL_REG(0x09), |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | |
| AD8460_CURRENT_LIMIT_CONV(abs(tmp[0]))); |
| } |
| |
| ret = device_property_read_u32_array(dev, "adi,range-microvolt", |
| tmp, ARRAY_SIZE(tmp)); |
| if (!ret) { |
| if (in_range(tmp[1], 0, AD8460_ABS_MAX_OVERVOLTAGE_UV)) |
| regmap_write(state->regmap, AD8460_CTRL_REG(0x0A), |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | |
| AD8460_VOLTAGE_LIMIT_CONV(tmp[1])); |
| |
| if (in_range(tmp[0], -AD8460_ABS_MAX_OVERVOLTAGE_UV, 0)) |
| regmap_write(state->regmap, AD8460_CTRL_REG(0x0B), |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | |
| AD8460_VOLTAGE_LIMIT_CONV(abs(tmp[0]))); |
| } |
| |
| ret = device_property_read_u32(dev, "adi,max-millicelsius", &temp); |
| if (!ret) { |
| if (in_range(temp, AD8460_MIN_OVERTEMPERATURE_MC, |
| AD8460_MAX_OVERTEMPERATURE_MC)) |
| regmap_write(state->regmap, AD8460_CTRL_REG(0x0C), |
| FIELD_PREP(AD8460_FAULT_ARM_MSK, 1) | |
| AD8460_TEMP_LIMIT_CONV(abs(temp))); |
| } |
| |
| ret = ad8460_reset(state); |
| if (ret) |
| return ret; |
| |
| /* Enables DAC by default */ |
| ret = regmap_clear_bits(state->regmap, AD8460_CTRL_REG(0x01), |
| AD8460_HVDAC_SLEEP_MSK); |
| if (ret) |
| return ret; |
| |
| indio_dev->modes = INDIO_DIRECT_MODE; |
| indio_dev->setup_ops = &ad8460_buffer_setup_ops; |
| |
| ret = devm_iio_dmaengine_buffer_setup_ext(dev, indio_dev, "tx", |
| IIO_BUFFER_DIRECTION_OUT); |
| if (ret) |
| return dev_err_probe(dev, ret, |
| "Failed to get DMA buffer\n"); |
| |
| return devm_iio_device_register(dev, indio_dev); |
| } |
| |
| static const struct of_device_id ad8460_of_match[] = { |
| { .compatible = "adi,ad8460" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, ad8460_of_match); |
| |
| static const struct spi_device_id ad8460_spi_match[] = { |
| { .name = "ad8460" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(spi, ad8460_spi_match); |
| |
| static struct spi_driver ad8460_driver = { |
| .driver = { |
| .name = "ad8460", |
| .of_match_table = ad8460_of_match, |
| }, |
| .probe = ad8460_probe, |
| .id_table = ad8460_spi_match, |
| }; |
| module_spi_driver(ad8460_driver); |
| |
| MODULE_AUTHOR("Mariel Tinaco <mariel.tinaco@analog.com"); |
| MODULE_DESCRIPTION("AD8460 DAC driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); |