Adding the Texas Instruments single channel 16 bit DAC driver. This device has configurable voltage and current output with 16 bit resolution for offset and gain. Signed-off-by: Dan Murphy <dmurphy@xxxxxx> --- drivers/iio/dac/Kconfig | 7 + drivers/iio/dac/Makefile | 1 + drivers/iio/dac/ti-dac8771.c | 499 +++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+) create mode 100644 drivers/iio/dac/ti-dac8771.c diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index 979070196da9..df7793113415 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -386,6 +386,13 @@ config TI_DAC7612 If compiled as a module, it will be called ti-dac7612. +config TI_DAC8771 + tristate "Texas Instruments 16-bit single channel DAC driver" + depends on SPI_MASTER && GPIOLIB + help + Driver for the Texas Instruments DAC8771. + If compiled as a module, it will be called ti-dac8771. + config VF610_DAC tristate "Vybrid vf610 DAC driver" depends on OF diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 1369fa1d2f0e..4aa6e35348cf 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -42,4 +42,5 @@ obj-$(CONFIG_TI_DAC082S085) += ti-dac082s085.o obj-$(CONFIG_TI_DAC5571) += ti-dac5571.o obj-$(CONFIG_TI_DAC7311) += ti-dac7311.o obj-$(CONFIG_TI_DAC7612) += ti-dac7612.o +obj-$(CONFIG_TI_DAC8771) += ti-dac8771.o obj-$(CONFIG_VF610_DAC) += vf610_dac.o diff --git a/drivers/iio/dac/ti-dac8771.c b/drivers/iio/dac/ti-dac8771.c new file mode 100644 index 000000000000..309728212a07 --- /dev/null +++ b/drivers/iio/dac/ti-dac8771.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +// DAC8771 DAC +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/ + +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/gpio/consumer.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> + +#include <linux/iio/iio.h> +#include <linux/iio/events.h> + +#define DAC8771_RESET 0x01 +#define DAC8771_RESET_CFG 0x02 +#define DAC8771_DAC_SELECT 0x03 +#define DAC8771_DAC_CFG 0x04 +#define DAC8771_DAC_DATA 0x05 +#define DAC8771_BUCK_BOOST_SEL 0x06 +#define DAC8771_BUCK_BOOST_CFG 0x07 +#define DAC8771_CH_CAL_EN 0x08 +#define DAC8771_CH_GAIN_CAL 0x09 +#define DAC8771_CH_OFF_CAL 0x0a +#define DAC8771_STATUS 0x0b +#define DAC8771_STATUS_MSK 0x0c +#define DAC8771_ALARM_ACTION 0x0d +#define DAC8771_ALARM_CODE 0x0e +#define DAC8771_WDOG_RESET 0x10 +#define DAC8771_DEV_ID 0x11 + +#define DAC8771_RESOLUTION 16 + +/* Status register */ +#define DAC8771_OVERTEMP BIT(4) +#define DAC8771_DAC_FAULT BIT(0) +#define DAC8771_STATUS_MASK (DAC8771_OVERTEMP | DAC8771_DAC_FAULT) + +#define DAC8771_READ_EN BIT(7) + +#define DAC8771_SW_RESET BIT(0) +#define DAC8771_SPI_ENABLE 0 +#define DAC8771_SPI_DISABLE 1 + +#define DAC8771_CHA_SEL BIT(5) +#define DAC8771_OUT_RANGE_MSK GENMASK(3, 0) +#define DAC8771_MAX_GAIN_OFF 65535 + +#define DAC8771_OUT_RANGE_DEF 0x0 +#define DAC8771_OUT_RANGE_CURR_LOW 4 +#define DAC8771_OUT_RANGE_CURR_HIGH 7 +#define DAC8771_OUT_RANGE_MAX 12 + +#define DAC8771_SELECT_A BIT(0) + +/** + * struct dac8771_chip - TI DAC chip + * @lock: protects write sequences + * @vref: regulator generating Vref + * @spi: SPI device to send data to the device + * @ldac: load DAC GPIO + * @reset: reset GPIO + * @chan_type: Type of IIO channel either VOLTAGE or CURRENT + * @output_range: output range of the device + * @val: cached value + * @buf: buffer for transfer data + */ +struct dac8771_chip { + struct mutex lock; + struct regulator *vref; + struct spi_device *spi; + struct gpio_desc *ldac; + struct gpio_desc *reset; + enum iio_chan_type chan_type; + u8 output_range; + u16 val; + union { + __be32 d32; + __be16 d16; + u8 d8[3]; + } data[3] ____cacheline_aligned; +}; + +static int dac8771_write(struct dac8771_chip *dac8771, + unsigned int reg, int val) +{ + u8 data[3]; + + data[0] = reg; + data[1] = val >> 8; + data[2] = val & 0xff; + + return spi_write(dac8771->spi, &data, sizeof(data)); +} + +static int dac8771_read(struct dac8771_chip *dac8771, unsigned int reg) +{ + int ret; + struct spi_transfer t[] = { + { + .tx_buf = &dac8771->data[0].d8[0], + .len = 3, + .cs_change = 1, + }, { + .tx_buf = &dac8771->data[1].d8[0], + .rx_buf = &dac8771->data[2].d8[0], + .len = 3, + }, + }; + + dac8771->data[0].d8[0] = (DAC8771_READ_EN | reg); + + ret = spi_sync_transfer(dac8771->spi, t, ARRAY_SIZE(t)); + if (ret < 0) + return ret; + + return (dac8771->data[2].d8[1] << 8 | dac8771->data[2].d8[2]); +} + +static int dac8771_sync_write(struct dac8771_chip *dac8771, + unsigned int reg, int val) +{ + int ret; + + ret = dac8771_write(dac8771, reg, val); + if (ret) + return ret; + + if (dac8771->ldac) { + gpiod_set_value(dac8771->ldac, 1); + gpiod_set_value(dac8771->ldac, 0); + } + + return ret; +} + +static int dac8771_reset(struct dac8771_chip *dac8771) +{ + if (dac8771->reset) { + gpiod_set_value(dac8771->reset, 0); + gpiod_set_value(dac8771->reset, 1); + } else { + return dac8771_sync_write(dac8771, DAC8771_RESET, + DAC8771_SW_RESET); + } + + return 0; +} + +static int dac8771_update_bits(struct dac8771_chip *dac8771, unsigned int reg, + int mask, int val) +{ + int write_val; + int tmp; + + tmp = dac8771_read(dac8771, reg); + if (tmp < 0) + return tmp; + + write_val = (tmp & ~mask); + write_val |= val & mask; + + return dac8771_write(dac8771, reg, write_val); +} + +static int dac8771_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct dac8771_chip *dac8771 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + *val = dac8771->val; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = regulator_get_voltage(dac8771->vref); + if (ret < 0) + return ret; + + *val = ret / 1000; + *val2 = DAC8771_RESOLUTION; + return IIO_VAL_FRACTIONAL_LOG2; + case IIO_CHAN_INFO_OFFSET: + mutex_lock(&dac8771->lock); + ret = dac8771_read(dac8771, DAC8771_CH_OFF_CAL); + mutex_unlock(&dac8771->lock); + + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&dac8771->lock); + ret = dac8771_read(dac8771, DAC8771_CH_GAIN_CAL); + mutex_unlock(&dac8771->lock); + + if (ret < 0) + return ret; + + *val = ret; + return IIO_VAL_INT; + default: + ret = -EINVAL; + } + + return -EINVAL; +} + +static int dac8771_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct dac8771_chip *dac8771 = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + mutex_lock(&dac8771->lock); + ret = dac8771_sync_write(dac8771, DAC8771_DAC_DATA, val); + if (!ret) + dac8771->val = val; + break; + case IIO_CHAN_INFO_ENABLE: + if (val) + val = DAC8771_CHA_SEL; + else + val = 0; + + mutex_lock(&dac8771->lock); + + ret = dac8771_update_bits(dac8771, DAC8771_DAC_SELECT, + DAC8771_CHA_SEL, val); + if (ret) + goto io_err; + + ret = dac8771_update_bits(dac8771, DAC8771_DAC_CFG, + DAC8771_OUT_RANGE_MSK, + dac8771->output_range); + break; + case IIO_CHAN_INFO_OFFSET: + if (val > DAC8771_MAX_GAIN_OFF) + return -EINVAL; + + mutex_lock(&dac8771->lock); + ret = dac8771_sync_write(dac8771, DAC8771_CH_OFF_CAL, val); + break; + case IIO_CHAN_INFO_HARDWAREGAIN: + if (val > DAC8771_MAX_GAIN_OFF) + return -EINVAL; + + mutex_lock(&dac8771->lock); + ret = dac8771_sync_write(dac8771, DAC8771_CH_GAIN_CAL, val); + break; + default: + return -EINVAL; + } +io_err: + mutex_unlock(&dac8771->lock); + return ret; +} + +static int dac8771_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + return IIO_VAL_INT; +} + +static irqreturn_t dac8771_event_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct dac8771_chip *dac8771 = iio_priv(indio_dev); + int clear_bits = 0; + int ret; + + ret = dac8771_read(dac8771, DAC8771_STATUS); + + if (ret & DAC8771_OVERTEMP) { + iio_push_event(private, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, + 0, IIO_EV_TYPE_THRESH, IIO_EV_DIR_RISING), + iio_get_time_ns(private)); + clear_bits = DAC8771_OVERTEMP; + } + + if (ret & DAC8771_DAC_FAULT) { + iio_push_event(private, + IIO_UNMOD_EVENT_CODE(dac8771->chan_type, + 0, IIO_EV_TYPE_CHANGE, IIO_EV_DIR_NONE), + iio_get_time_ns(private)); + + clear_bits |= DAC8771_DAC_FAULT; + } + + ret = dac8771_update_bits(dac8771, DAC8771_STATUS, DAC8771_STATUS_MASK, + clear_bits); + if (ret) + dev_err(&dac8771->spi->dev, + "Failed to clear status register\n"); + + return IRQ_HANDLED; +} + +static const struct iio_info dac8771_info = { + .read_raw = dac8771_read_raw, + .write_raw = dac8771_write_raw, + .write_raw_get_fmt = dac8771_write_raw_get_fmt, +}; + +static int dac8771_probe_dt(struct dac8771_chip *dac8771) +{ + u32 range; + int ret; + + dac8771->ldac = devm_gpiod_get_optional(&dac8771->spi->dev, "loaddacs", + GPIOD_OUT_HIGH); + if (IS_ERR(dac8771->ldac)) { + ret = PTR_ERR(dac8771->ldac); + if (ret != -ENODEV) { + if (ret != -EPROBE_DEFER) + dev_err(&dac8771->spi->dev, + "Failed to get loaddacs gpio: %d\n", + ret); + return ret; + } + dac8771->ldac = NULL; + } + + dac8771->reset = devm_gpiod_get_optional(&dac8771->spi->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(dac8771->reset)) { + ret = PTR_ERR(dac8771->reset); + if (ret != -ENODEV) { + if (ret != -EPROBE_DEFER) + dev_err(&dac8771->spi->dev, + "Failed to get reset gpio: %d\n", + ret); + return ret; + } + dac8771->reset = NULL; + } + + ret = device_property_read_u32(&dac8771->spi->dev, "ti,output-range", + &range); + if (ret) { + dev_err(&dac8771->spi->dev, + "Output range is not set using default\n"); + range = DAC8771_OUT_RANGE_DEF; + } else { + if (dac8771->output_range < DAC8771_OUT_RANGE_DEF || + dac8771->output_range > DAC8771_OUT_RANGE_MAX) { + dev_err(&dac8771->spi->dev, + "Output range is invalid\n"); + return -EINVAL; + } + } + + dac8771->output_range = range; + if ((dac8771->output_range >= DAC8771_OUT_RANGE_CURR_LOW && + dac8771->output_range <= DAC8771_OUT_RANGE_CURR_HIGH) || + dac8771->output_range == DAC8771_OUT_RANGE_MAX) + dac8771->chan_type = IIO_CURRENT; + else + dac8771->chan_type = IIO_VOLTAGE; + + return 0; +} + +static int dac8771_init(struct dac8771_chip *dac8771) +{ + int ret; + + ret = dac8771_reset(dac8771); + if (ret) + return ret; + + return dac8771_write(dac8771, DAC8771_BUCK_BOOST_SEL, DAC8771_SELECT_A); +} + +static int dac8771_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct dac8771_chip *dac8771; + struct iio_dev *indio_dev; + struct iio_chan_spec *chan; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*dac8771)); + if (!indio_dev) { + dev_err(dev, "can not allocate iio device\n"); + return -ENOMEM; + } + + spi->mode = SPI_MODE_2; + spi_setup(spi); + spi_set_drvdata(spi, indio_dev); + + dac8771 = iio_priv(indio_dev); + dac8771->spi = spi; + + ret = dac8771_probe_dt(dac8771); + if (ret) + return ret; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = spi->dev.of_node; + indio_dev->info = &dac8771_info; + indio_dev->name = spi_get_device_id(spi)->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->num_channels = 1; + + chan = devm_kzalloc(&indio_dev->dev, sizeof(struct iio_chan_spec), + GFP_KERNEL); + + chan->type = dac8771->chan_type; + chan->channel = 0; + chan->output = true; + chan->info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE); + chan->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_OFFSET) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE); + + indio_dev->channels = chan; + + dac8771->vref = devm_regulator_get(&dac8771->spi->dev, "vref"); + if (IS_ERR(dac8771->vref)) { + dev_err(&dac8771->spi->dev, "error to get regulator\n"); + return PTR_ERR(dac8771->vref); + } + + ret = regulator_enable(dac8771->vref); + if (ret < 0) { + dev_err(dev, "can not enable regulator\n"); + return ret; + } + + mutex_init(&dac8771->lock); + + if (spi->irq) { + ret = devm_request_threaded_irq(&spi->dev, spi->irq, NULL, + &dac8771_event_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + spi_get_device_id(dac8771->spi)->name, + indio_dev); + if (ret) + goto err; + } + + ret = dac8771_init(dac8771); + if (ret) + goto err; + + return devm_iio_device_register(&spi->dev, indio_dev); +err: + mutex_destroy(&dac8771->lock); + regulator_disable(dac8771->vref); + return ret; +} + +static int dac8771_remove(struct spi_device *spi) +{ + struct iio_dev *indio_dev = spi_get_drvdata(spi); + struct dac8771_chip *dac8771 = iio_priv(indio_dev); + + mutex_destroy(&dac8771->lock); + regulator_disable(dac8771->vref); + return 0; +} + +static const struct of_device_id dac8771_of_id[] = { + { .compatible = "ti,dac8771" }, + { } +}; +MODULE_DEVICE_TABLE(of, dac8771_of_id); + +static const struct spi_device_id dac8771_spi_id[] = { + { "dac8771", 0 }, + { } +}; +MODULE_DEVICE_TABLE(spi, dac8771_spi_id); + +static struct spi_driver dac8771_driver = { + .driver = { + .name = "ti-dac8771", + .of_match_table = dac8771_of_id, + }, + .probe = dac8771_probe, + .remove = dac8771_remove, + .id_table = dac8771_spi_id, +}; +module_spi_driver(dac8771_driver); + +MODULE_AUTHOR("Dan Murphy <dmurphy@xxxxxx>"); +MODULE_DESCRIPTION("Texas Instruments DAC8771 driver"); +MODULE_LICENSE("GPL v2"); -- 2.23.0