All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
@ 2014-12-15 11:39 ` Nikolaus Schulz
  0 siblings, 0 replies; 12+ messages in thread
From: Nikolaus Schulz @ 2014-12-15 11:39 UTC (permalink / raw)
  To: linux-iio, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Nikolaus Schulz, devicetree, linux-kernel
  Cc: Alban Bedel

Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
---
 .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt

diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
new file mode 100644
index 0000000..32e96859
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
@@ -0,0 +1,22 @@
+TI DAC8554 Digital to Analog Converter
+
+This driver supports the SPI bus.
+
+Required properties:
+	- compatible: must be "ti,dac8554"
+	- vref-supply: the vref power supply
+	- ti,address: the additional 2-bit chip address
+
+For required properties on SPI, please consult
+Documentation/devicetree/bindings/spi/spi-bus.txt
+
+Example:
+
+	dac8554@0 {
+		compatible = "ti,dac8554";
+		reg = <0>;
+		spi-max-frequency = <50000000>;
+
+		vref-supply = <&vdd_vref>;
+		ti,address = <0>;
+	};
-- 
2.1.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
@ 2014-12-15 11:39 ` Nikolaus Schulz
  0 siblings, 0 replies; 12+ messages in thread
From: Nikolaus Schulz @ 2014-12-15 11:39 UTC (permalink / raw)
  To: linux-iio-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Nikolaus Schulz,
	devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA
  Cc: Alban Bedel

Signed-off-by: Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
---
 .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt

diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
new file mode 100644
index 0000000..32e96859
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
@@ -0,0 +1,22 @@
+TI DAC8554 Digital to Analog Converter
+
+This driver supports the SPI bus.
+
+Required properties:
+	- compatible: must be "ti,dac8554"
+	- vref-supply: the vref power supply
+	- ti,address: the additional 2-bit chip address
+
+For required properties on SPI, please consult
+Documentation/devicetree/bindings/spi/spi-bus.txt
+
+Example:
+
+	dac8554@0 {
+		compatible = "ti,dac8554";
+		reg = <0>;
+		spi-max-frequency = <50000000>;
+
+		vref-supply = <&vdd_vref>;
+		ti,address = <0>;
+	};
-- 
2.1.3

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH v3 2/2] iio: add driver for the TI DAC8554
  2014-12-15 11:39 ` Nikolaus Schulz
  (?)
@ 2014-12-15 11:39 ` Nikolaus Schulz
  2014-12-18 21:30     ` Hartmut Knaack
  2014-12-26 11:51   ` Jonathan Cameron
  -1 siblings, 2 replies; 12+ messages in thread
From: Nikolaus Schulz @ 2014-12-15 11:39 UTC (permalink / raw)
  To: linux-iio, Jonathan Cameron, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald, Grant Likely, Rob Herring, Michael Welling,
	Philippe Reynes, Nikolaus Schulz, linux-kernel, devicetree
  Cc: Alban Bedel

The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
interface.

Changes in v3:
* Small fixes in the documentation of struct dac8554_state
* Replace some magic constants with macros
* Replace memset on powerdown state arrays with explicit loop
* If probing fails due to invalid DT data, return -EINVAL instead of
  -ENODEV
* Add driver dependency on regulator support
* Move regulator_get_voltage call from probe time to dac8554_read_raw
* Rename _(read|write)_raw argument "mask" to "info"
* Make iio_chan_spec variable static
* DT: add vendor prefix to device specific "address" property
* Whitespace fixes

Changes in v2:
* Use DMA-safe buffer for SPI transfer
* Normalize powerdown_mode name "hi-z" to "three_state" as per
  ABI/testing/sysfs-bus-iio
* Register device late in probe function
* Avoid powerdown broadcast update, which touches all DAC on the bus

Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
---
 drivers/iio/dac/Kconfig      |  11 ++
 drivers/iio/dac/Makefile     |   1 +
 drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 393 insertions(+)
 create mode 100644 drivers/iio/dac/ti-dac8554.c

diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 2236ea2..d3b21c853b5 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -181,4 +181,15 @@ config MCP4922
 	  To compile this driver as a module, choose M here: the module
 	  will be called mcp4922.
 
+config TI_DAC8554
+	tristate "TI DAC8554 driver"
+	depends on SPI
+	depends on OF
+	depends on REGULATOR
+	help
+	  Say yes here to build the driver for the TI DAC8554.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called ti-dac8554.
+
 endmenu
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 52be7e1..b98d79a 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
 obj-$(CONFIG_MAX5821) += max5821.o
 obj-$(CONFIG_MCP4725) += mcp4725.o
 obj-$(CONFIG_MCP4922) += mcp4922.o
+obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
new file mode 100644
index 0000000..84b9f42
--- /dev/null
+++ b/drivers/iio/dac/ti-dac8554.c
@@ -0,0 +1,381 @@
+/*
+ * TI DAC8554 Digital to Analog Converter SPI driver
+ *
+ * Copyright (C) 2014 Avionic Design GmbH
+ *
+ * Based on ad5446r_spi.c
+ * Copyright (C) 2010,2011 Analog Devices Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/spi/spi.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <linux/iio/iio.h>
+#include <linux/of.h>
+
+#define DAC8554_DRIVERNAME			"ti-dac8554"
+#define DAC8554_DAC_CHANNELS			4
+#define DAC8554_DATABITS			16
+
+/* Load commands */
+#define DAC8554_CMD_STORE_DAC_N			0x0
+#define DAC8554_CMD_UPDATE_DAC_N		0x1
+#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
+#define DAC8554_CMD_UPDATE_BROADCAST		0x3
+
+#define DAC8554_BROADCAST_USE_SRDATA		0x2
+
+/* Powerdown modes (PD1 | PD2 bits) */
+#define DAC8554_PWRDN_HIZ			0x0
+#define DAC8554_PWRDN_1K			0x1
+#define DAC8554_PWRDN_100K			0x2
+
+/* Input shift register composition */
+#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
+#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
+#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
+#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
+
+/**
+ * struct dac8554_state - driver instance specific data
+ * @spi:		SPI device
+ * @reg:		supply regulator
+ * @addr:		two-bit chip address
+ * @val:		channel data
+ * @powerdown:		channel powerdown flag
+ * @powerdown_mode:	channel powerdown mode
+ * @xfer:		SPI transfer buffer
+ */
+struct dac8554_state {
+	struct spi_device		*spi;
+	struct regulator		*reg;
+	unsigned			addr;
+	u16				val[DAC8554_DAC_CHANNELS];
+	bool				powerdown[DAC8554_DAC_CHANNELS];
+	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
+
+	/*
+	 * DMA (thus cache coherency maintenance) requires the
+	 * transfer buffers to live in their own cache lines.
+	 */
+	u8				xfer[3] ____cacheline_aligned;
+};
+
+static int dac8554_spi_write(struct dac8554_state *st,
+			     unsigned cmd,
+			     unsigned chan_addr,
+			     unsigned val)
+{
+	u32 data;
+
+	/*
+	 * The input shift register is 24 bits wide. The 8 MSB are
+	 * control bits, followed by 16 data bits.
+	 * The first two bits A1 and A0 address a DAC8554 chip.
+	 * The next two are the command bits, LD1 and LD0.
+	 * After a don't-care-bit, the next two bits select the channel.
+	 * The final control bit PD0 is a flag signalling if the data
+	 * bits encode a powerdown mode. We merge PD0 with the adjacent
+	 * data bits.
+	 */
+
+	if (cmd > 3 || chan_addr > 3 ||
+			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
+		return -EINVAL;
+
+	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
+	       DAC8554_CHAN_TO_SR(chan_addr) | val;
+
+	st->xfer[0] = data >> 16;
+	st->xfer[1] = data >> 8;
+	st->xfer[2] = data;
+
+	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
+}
+
+static int dac8554_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan,
+			    int *val,
+			    int *val2,
+			    long info)
+{
+	struct dac8554_state *st = iio_priv(indio_dev);
+	int voltage_uv;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		*val = st->val[chan->address];
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		voltage_uv = regulator_get_voltage(st->reg);
+		if (voltage_uv < 0)
+			return voltage_uv;
+		*val = voltage_uv / 1000;
+		*val2 = DAC8554_DATABITS;
+		return IIO_VAL_FRACTIONAL_LOG2;
+	}
+	return -EINVAL;
+}
+
+static int dac8554_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val,
+			     int val2,
+			     long info)
+{
+	struct dac8554_state *st = iio_priv(indio_dev);
+	int err;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		if (val > 0xffff || val < 0)
+			return -EINVAL;
+
+		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
+					chan->address, val);
+		if (err)
+			return err;
+
+		st->val[chan->address] = val;
+
+		/* By hw design, DAC updates automatically trigger powerup. */
+		st->powerdown[chan->address] = false;
+
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan)
+{
+	struct dac8554_state *st = iio_priv(indio_dev);
+
+	return st->powerdown_mode[chan->address];
+}
+
+static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
+				      const struct iio_chan_spec *chan,
+				      unsigned int mode)
+{
+	struct dac8554_state *st = iio_priv(indio_dev);
+
+	st->powerdown_mode[chan->address] = mode;
+
+	return 0;
+}
+
+static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
+					  uintptr_t private,
+					  const struct iio_chan_spec *chan,
+					  char *buf)
+{
+	struct dac8554_state *st = iio_priv(indio_dev);
+
+	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
+}
+
+static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
+					   uintptr_t private,
+					   const struct iio_chan_spec *chan,
+					   const char *buf,
+					   size_t len)
+{
+	bool powerdown;
+	int ret;
+	struct dac8554_state *st = iio_priv(indio_dev);
+	u8 powerdown_mode;
+
+	ret = strtobool(buf, &powerdown);
+	if (ret)
+		return ret;
+
+	st->powerdown[chan->address] = powerdown;
+
+	if (powerdown) {
+		powerdown_mode = st->powerdown_mode[chan->address];
+		ret = dac8554_spi_write(st,
+					DAC8554_CMD_UPDATE_DAC_N,
+					chan->address,
+					DAC8554_PWRDN_TO_SR(powerdown_mode));
+	} else {
+		/* Load DAC with cached value. This triggers a powerup. */
+		ret = dac8554_spi_write(st,
+					DAC8554_CMD_UPDATE_DAC_N,
+					chan->address,
+					st->val[chan->address]);
+	}
+
+	if (ret)
+		return ret;
+
+	return len;
+}
+
+static int dac8554_powerdown(struct dac8554_state *st,
+			     u8 powerdown_mode)
+{
+	int chan, cmd, ret;
+
+	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
+		cmd = chan ? DAC8554_CMD_STORE_DAC_N
+			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
+		ret = dac8554_spi_write(st, cmd, chan,
+					DAC8554_PWRDN_TO_SR(powerdown_mode));
+		if (ret)
+			return ret;
+	}
+
+	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
+		st->powerdown_mode[chan] = powerdown_mode;
+		st->powerdown[chan] = true;
+	}
+
+	return 0;
+}
+
+static const struct iio_info dac8554_info = {
+	.write_raw = dac8554_write_raw,
+	.read_raw = dac8554_read_raw,
+	.driver_module = THIS_MODULE,
+};
+
+static const char * const dac8554_powerdown_modes[] = {
+	"three_state",
+	"1kohm_to_gnd",
+	"100kohm_to_gnd"
+};
+
+static const struct iio_enum dac8854_powerdown_mode_enum = {
+	.items = dac8554_powerdown_modes,
+	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
+	.get = dac8554_get_powerdown_mode,
+	.set = dac8554_set_powerdown_mode,
+};
+
+static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
+	{
+		.name = "powerdown",
+		.read = dac8554_read_dac_powerdown,
+		.write = dac8554_write_dac_powerdown,
+		.shared = IIO_SEPARATE,
+	},
+	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
+		 &dac8854_powerdown_mode_enum),
+	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
+	{ },
+};
+
+#define DAC8554_CHANNEL(chan) { \
+	.type = IIO_VOLTAGE, \
+	.indexed = 1, \
+	.output = 1, \
+	.channel = (chan), \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+	.address = (chan), \
+	.ext_info = dac8554_ext_info, \
+}
+static const struct iio_chan_spec dac8554_channels[] = {
+	DAC8554_CHANNEL(0),
+	DAC8554_CHANNEL(1),
+	DAC8554_CHANNEL(2),
+	DAC8554_CHANNEL(3),
+};
+#undef DAC8554_CHANNEL
+
+static int dac8554_probe(struct spi_device *spi)
+{
+	struct dac8554_state *st;
+	struct iio_dev *indio_dev;
+	int ret;
+	u32 addr;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	indio_dev->dev.parent = &spi->dev;
+	indio_dev->name = DAC8554_DRIVERNAME;
+	indio_dev->info = &dac8554_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = dac8554_channels;
+	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
+
+	spi_set_drvdata(spi, indio_dev);
+
+	st = iio_priv(indio_dev);
+
+	if (!spi->dev.of_node) {
+		dev_err(&spi->dev, "missing OF node");
+		return -EINVAL;
+	}
+	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
+	if (ret || addr < 0 || addr > 2) {
+		dev_err(&spi->dev, "no or invalid chip address");
+		return -EINVAL;
+	}
+
+	st->spi = spi;
+	st->addr = addr;
+
+	st->reg = devm_regulator_get(&spi->dev, "vref");
+	if (IS_ERR(st->reg))
+		return PTR_ERR(st->reg);
+
+	ret = regulator_enable(st->reg);
+	if (ret)
+		return ret;
+
+	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
+	if (ret)
+		goto error_disable_reg;
+
+	ret = iio_device_register(indio_dev);
+	if (ret)
+		goto error_disable_reg;
+
+	return 0;
+
+error_disable_reg:
+	regulator_disable(st->reg);
+	return ret;
+}
+
+static int dac8554_remove(struct spi_device *spi)
+{
+	struct iio_dev *indio_dev = spi_get_drvdata(spi);
+	struct dac8554_state *st = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	regulator_disable(st->reg);
+
+	return 0;
+}
+
+static const struct of_device_id dac8554_of_match[] = {
+	{ .compatible = "ti,dac8554" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, dac8554_of_match);
+
+static struct spi_driver dac8554_driver = {
+	.driver = {
+		.name = DAC8554_DRIVERNAME,
+		.owner = THIS_MODULE,
+		.of_match_table = dac8554_of_match,
+	 },
+	.probe = dac8554_probe,
+	.remove = dac8554_remove,
+};
+module_spi_driver(dac8554_driver);
+
+MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz@avionic-design.de>");
+MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
+MODULE_LICENSE("GPL v2");
-- 
2.1.3


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
@ 2014-12-15 13:02   ` Mark Rutland
  0 siblings, 0 replies; 12+ messages in thread
From: Mark Rutland @ 2014-12-15 13:02 UTC (permalink / raw)
  To: Nikolaus Schulz
  Cc: linux-iio, Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala,
	devicetree, linux-kernel, Alban Bedel

On Mon, Dec 15, 2014 at 11:39:56AM +0000, Nikolaus Schulz wrote:
> Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
> ---
>  .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
>  1 file changed, 22 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> 
> diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> new file mode 100644
> index 0000000..32e96859
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> @@ -0,0 +1,22 @@
> +TI DAC8554 Digital to Analog Converter
> +
> +This driver supports the SPI bus.

The binding need not refer to the driver, this sentence can go. It might
be worth adding "(SPI)" to the first line.

> +
> +Required properties:
> +	- compatible: must be "ti,dac8554"
> +	- vref-supply: the vref power supply
> +	- ti,address: the additional 2-bit chip address
> +
> +For required properties on SPI, please consult
> +Documentation/devicetree/bindings/spi/spi-bus.txt
> +
> +Example:
> +
> +	dac8554@0 {
> +		compatible = "ti,dac8554";
> +		reg = <0>;
> +		spi-max-frequency = <50000000>;
> +
> +		vref-supply = <&vdd_vref>;
> +		ti,address = <0>;

What's this property used for?

> +	};

Otherwise this looks sane to me.

Mark.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
@ 2014-12-15 13:02   ` Mark Rutland
  0 siblings, 0 replies; 12+ messages in thread
From: Mark Rutland @ 2014-12-15 13:02 UTC (permalink / raw)
  To: Nikolaus Schulz
  Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Pawel Moll,
	Ian Campbell, Kumar Gala, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Alban Bedel

On Mon, Dec 15, 2014 at 11:39:56AM +0000, Nikolaus Schulz wrote:
> Signed-off-by: Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
> ---
>  .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
>  1 file changed, 22 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> 
> diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> new file mode 100644
> index 0000000..32e96859
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> @@ -0,0 +1,22 @@
> +TI DAC8554 Digital to Analog Converter
> +
> +This driver supports the SPI bus.

The binding need not refer to the driver, this sentence can go. It might
be worth adding "(SPI)" to the first line.

> +
> +Required properties:
> +	- compatible: must be "ti,dac8554"
> +	- vref-supply: the vref power supply
> +	- ti,address: the additional 2-bit chip address
> +
> +For required properties on SPI, please consult
> +Documentation/devicetree/bindings/spi/spi-bus.txt
> +
> +Example:
> +
> +	dac8554@0 {
> +		compatible = "ti,dac8554";
> +		reg = <0>;
> +		spi-max-frequency = <50000000>;
> +
> +		vref-supply = <&vdd_vref>;
> +		ti,address = <0>;

What's this property used for?

> +	};

Otherwise this looks sane to me.

Mark.

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
  2014-12-15 13:02   ` Mark Rutland
@ 2014-12-15 13:15     ` Nikolaus Schulz
  -1 siblings, 0 replies; 12+ messages in thread
From: Nikolaus Schulz @ 2014-12-15 13:15 UTC (permalink / raw)
  To: Mark Rutland
  Cc: linux-iio, Rob Herring, Pawel Moll, Ian Campbell, Kumar Gala,
	devicetree, linux-kernel, Alban Bedel

On Mon, Dec 15, 2014 at 01:02:48PM +0000, Mark Rutland wrote:
> On Mon, Dec 15, 2014 at 11:39:56AM +0000, Nikolaus Schulz wrote:
> > Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
> > ---
> >  .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
> >  1 file changed, 22 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > new file mode 100644
> > index 0000000..32e96859
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > @@ -0,0 +1,22 @@
> > +TI DAC8554 Digital to Analog Converter
> > +
> > +This driver supports the SPI bus.
> 
> The binding need not refer to the driver, this sentence can go. It might
> be worth adding "(SPI)" to the first line.

OK

> > +
> > +Required properties:
> > +	- compatible: must be "ti,dac8554"
> > +	- vref-supply: the vref power supply
> > +	- ti,address: the additional 2-bit chip address
> > +
> > +For required properties on SPI, please consult
> > +Documentation/devicetree/bindings/spi/spi-bus.txt
> > +
> > +Example:
> > +
> > +	dac8554@0 {
> > +		compatible = "ti,dac8554";
> > +		reg = <0>;
> > +		spi-max-frequency = <50000000>;
> > +
> > +		vref-supply = <&vdd_vref>;
> > +		ti,address = <0>;
> 
> What's this property used for?

The DAC8554 has its own addressing scheme, where each chip is assigned a
two-bit address, defined by the state of two pins. So up to 4 DAC8554
can be operated independently on the same SPI bus.

> > +	};
> 
> Otherwise this looks sane to me.
> 
> Mark.

-- 
Avionic Design GmbH
Nikolaus Schulz
Wragekamp 10
D-22397 Hamburg
Germany

Tel.:  +49 40 88187-163
Fax:   +49 40 88187-150
Email: nikolaus.schulz@avionic-design.de

Avionic Design GmbH
Amtsgericht Hamburg HRB 82598
Geschäftsführung: Cornelis Broers
Ust.-Ident-Nr.: DE813378254

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings
@ 2014-12-15 13:15     ` Nikolaus Schulz
  0 siblings, 0 replies; 12+ messages in thread
From: Nikolaus Schulz @ 2014-12-15 13:15 UTC (permalink / raw)
  To: Mark Rutland
  Cc: linux-iio-u79uwXL29TY76Z2rM5mHXA, Rob Herring, Pawel Moll,
	Ian Campbell, Kumar Gala, devicetree-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA, Alban Bedel

On Mon, Dec 15, 2014 at 01:02:48PM +0000, Mark Rutland wrote:
> On Mon, Dec 15, 2014 at 11:39:56AM +0000, Nikolaus Schulz wrote:
> > Signed-off-by: Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
> > ---
> >  .../devicetree/bindings/iio/dac/ti-dac8554.txt     | 22 ++++++++++++++++++++++
> >  1 file changed, 22 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > new file mode 100644
> > index 0000000..32e96859
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/dac/ti-dac8554.txt
> > @@ -0,0 +1,22 @@
> > +TI DAC8554 Digital to Analog Converter
> > +
> > +This driver supports the SPI bus.
> 
> The binding need not refer to the driver, this sentence can go. It might
> be worth adding "(SPI)" to the first line.

OK

> > +
> > +Required properties:
> > +	- compatible: must be "ti,dac8554"
> > +	- vref-supply: the vref power supply
> > +	- ti,address: the additional 2-bit chip address
> > +
> > +For required properties on SPI, please consult
> > +Documentation/devicetree/bindings/spi/spi-bus.txt
> > +
> > +Example:
> > +
> > +	dac8554@0 {
> > +		compatible = "ti,dac8554";
> > +		reg = <0>;
> > +		spi-max-frequency = <50000000>;
> > +
> > +		vref-supply = <&vdd_vref>;
> > +		ti,address = <0>;
> 
> What's this property used for?

The DAC8554 has its own addressing scheme, where each chip is assigned a
two-bit address, defined by the state of two pins. So up to 4 DAC8554
can be operated independently on the same SPI bus.

> > +	};
> 
> Otherwise this looks sane to me.
> 
> Mark.

-- 
Avionic Design GmbH
Nikolaus Schulz
Wragekamp 10
D-22397 Hamburg
Germany

Tel.:  +49 40 88187-163
Fax:   +49 40 88187-150
Email: nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org

Avionic Design GmbH
Amtsgericht Hamburg HRB 82598
Geschäftsführung: Cornelis Broers
Ust.-Ident-Nr.: DE813378254

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] iio: add driver for the TI DAC8554
@ 2014-12-18 21:30     ` Hartmut Knaack
  0 siblings, 0 replies; 12+ messages in thread
From: Hartmut Knaack @ 2014-12-18 21:30 UTC (permalink / raw)
  To: Nikolaus Schulz, linux-iio, Jonathan Cameron, Lars-Peter Clausen,
	Peter Meerwald, Grant Likely, Rob Herring, Michael Welling,
	Philippe Reynes, linux-kernel, devicetree
  Cc: Alban Bedel

Nikolaus Schulz schrieb am 15.12.2014 um 12:39:
> The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
> interface.
> 
> Changes in v3:
> * Small fixes in the documentation of struct dac8554_state
> * Replace some magic constants with macros
> * Replace memset on powerdown state arrays with explicit loop
> * If probing fails due to invalid DT data, return -EINVAL instead of
>   -ENODEV
> * Add driver dependency on regulator support
> * Move regulator_get_voltage call from probe time to dac8554_read_raw
> * Rename _(read|write)_raw argument "mask" to "info"
> * Make iio_chan_spec variable static
> * DT: add vendor prefix to device specific "address" property
> * Whitespace fixes
> 
> Changes in v2:
> * Use DMA-safe buffer for SPI transfer
> * Normalize powerdown_mode name "hi-z" to "three_state" as per
>   ABI/testing/sysfs-bus-iio
> * Register device late in probe function
> * Avoid powerdown broadcast update, which touches all DAC on the bus
> 
> Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
Reviewed-by: Hartmut Knaack <knaack.h@gmx.de>
> ---
>  drivers/iio/dac/Kconfig      |  11 ++
>  drivers/iio/dac/Makefile     |   1 +
>  drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 393 insertions(+)
>  create mode 100644 drivers/iio/dac/ti-dac8554.c
> 
> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
> index 2236ea2..d3b21c853b5 100644
> --- a/drivers/iio/dac/Kconfig
> +++ b/drivers/iio/dac/Kconfig
> @@ -181,4 +181,15 @@ config MCP4922
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called mcp4922.
>  
> +config TI_DAC8554
> +	tristate "TI DAC8554 driver"
> +	depends on SPI
> +	depends on OF
> +	depends on REGULATOR
> +	help
> +	  Say yes here to build the driver for the TI DAC8554.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called ti-dac8554.
> +
>  endmenu
> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
> index 52be7e1..b98d79a 100644
> --- a/drivers/iio/dac/Makefile
> +++ b/drivers/iio/dac/Makefile
> @@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
>  obj-$(CONFIG_MAX5821) += max5821.o
>  obj-$(CONFIG_MCP4725) += mcp4725.o
>  obj-$(CONFIG_MCP4922) += mcp4922.o
> +obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
> diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
> new file mode 100644
> index 0000000..84b9f42
> --- /dev/null
> +++ b/drivers/iio/dac/ti-dac8554.c
> @@ -0,0 +1,381 @@
> +/*
> + * TI DAC8554 Digital to Analog Converter SPI driver
> + *
> + * Copyright (C) 2014 Avionic Design GmbH
> + *
> + * Based on ad5446r_spi.c
> + * Copyright (C) 2010,2011 Analog Devices Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/spi/spi.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/module.h>
> +#include <linux/iio/iio.h>
> +#include <linux/of.h>
> +
> +#define DAC8554_DRIVERNAME			"ti-dac8554"
> +#define DAC8554_DAC_CHANNELS			4
> +#define DAC8554_DATABITS			16
> +
> +/* Load commands */
> +#define DAC8554_CMD_STORE_DAC_N			0x0
> +#define DAC8554_CMD_UPDATE_DAC_N		0x1
> +#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
> +#define DAC8554_CMD_UPDATE_BROADCAST		0x3
> +
> +#define DAC8554_BROADCAST_USE_SRDATA		0x2
> +
> +/* Powerdown modes (PD1 | PD2 bits) */
> +#define DAC8554_PWRDN_HIZ			0x0
> +#define DAC8554_PWRDN_1K			0x1
> +#define DAC8554_PWRDN_100K			0x2
> +
> +/* Input shift register composition */
> +#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
> +#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
> +#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
> +#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
> +
> +/**
> + * struct dac8554_state - driver instance specific data
> + * @spi:		SPI device
> + * @reg:		supply regulator
> + * @addr:		two-bit chip address
> + * @val:		channel data
> + * @powerdown:		channel powerdown flag
> + * @powerdown_mode:	channel powerdown mode
> + * @xfer:		SPI transfer buffer
> + */
> +struct dac8554_state {
> +	struct spi_device		*spi;
> +	struct regulator		*reg;
> +	unsigned			addr;
> +	u16				val[DAC8554_DAC_CHANNELS];
> +	bool				powerdown[DAC8554_DAC_CHANNELS];
> +	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
> +
> +	/*
> +	 * DMA (thus cache coherency maintenance) requires the
> +	 * transfer buffers to live in their own cache lines.
> +	 */
> +	u8				xfer[3] ____cacheline_aligned;
> +};
> +
> +static int dac8554_spi_write(struct dac8554_state *st,
> +			     unsigned cmd,
> +			     unsigned chan_addr,
> +			     unsigned val)
> +{
> +	u32 data;
> +
> +	/*
> +	 * The input shift register is 24 bits wide. The 8 MSB are
> +	 * control bits, followed by 16 data bits.
> +	 * The first two bits A1 and A0 address a DAC8554 chip.
> +	 * The next two are the command bits, LD1 and LD0.
> +	 * After a don't-care-bit, the next two bits select the channel.
> +	 * The final control bit PD0 is a flag signalling if the data
> +	 * bits encode a powerdown mode. We merge PD0 with the adjacent
> +	 * data bits.
> +	 */
> +
> +	if (cmd > 3 || chan_addr > 3 ||
> +			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
> +		return -EINVAL;
> +
> +	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
> +	       DAC8554_CHAN_TO_SR(chan_addr) | val;
> +
> +	st->xfer[0] = data >> 16;
> +	st->xfer[1] = data >> 8;
> +	st->xfer[2] = data;
> +
> +	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
> +}
> +
> +static int dac8554_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val,
> +			    int *val2,
> +			    long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int voltage_uv;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		*val = st->val[chan->address];
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		voltage_uv = regulator_get_voltage(st->reg);
> +		if (voltage_uv < 0)
> +			return voltage_uv;
> +		*val = voltage_uv / 1000;
> +		*val2 = DAC8554_DATABITS;
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int dac8554_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val,
> +			     int val2,
> +			     long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int err;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (val > 0xffff || val < 0)
> +			return -EINVAL;
> +
> +		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address, val);
> +		if (err)
> +			return err;
> +
> +		st->val[chan->address] = val;
> +
> +		/* By hw design, DAC updates automatically trigger powerup. */
> +		st->powerdown[chan->address] = false;
> +
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return st->powerdown_mode[chan->address];
> +}
> +
> +static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan,
> +				      unsigned int mode)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	st->powerdown_mode[chan->address] = mode;
> +
> +	return 0;
> +}
> +
> +static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
> +					  uintptr_t private,
> +					  const struct iio_chan_spec *chan,
> +					  char *buf)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
> +}
> +
> +static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
> +					   uintptr_t private,
> +					   const struct iio_chan_spec *chan,
> +					   const char *buf,
> +					   size_t len)
> +{
> +	bool powerdown;
> +	int ret;
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	u8 powerdown_mode;
> +
> +	ret = strtobool(buf, &powerdown);
> +	if (ret)
> +		return ret;
> +
> +	st->powerdown[chan->address] = powerdown;
> +
> +	if (powerdown) {
> +		powerdown_mode = st->powerdown_mode[chan->address];
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +	} else {
> +		/* Load DAC with cached value. This triggers a powerup. */
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					st->val[chan->address]);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int dac8554_powerdown(struct dac8554_state *st,
> +			     u8 powerdown_mode)
> +{
> +	int chan, cmd, ret;
> +
> +	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
> +		cmd = chan ? DAC8554_CMD_STORE_DAC_N
> +			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
> +		ret = dac8554_spi_write(st, cmd, chan,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
> +		st->powerdown_mode[chan] = powerdown_mode;
> +		st->powerdown[chan] = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct iio_info dac8554_info = {
> +	.write_raw = dac8554_write_raw,
> +	.read_raw = dac8554_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static const char * const dac8554_powerdown_modes[] = {
> +	"three_state",
> +	"1kohm_to_gnd",
> +	"100kohm_to_gnd"
> +};
> +
> +static const struct iio_enum dac8854_powerdown_mode_enum = {
> +	.items = dac8554_powerdown_modes,
> +	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
> +	.get = dac8554_get_powerdown_mode,
> +	.set = dac8554_set_powerdown_mode,
> +};
> +
> +static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
> +	{
> +		.name = "powerdown",
> +		.read = dac8554_read_dac_powerdown,
> +		.write = dac8554_write_dac_powerdown,
> +		.shared = IIO_SEPARATE,
> +	},
> +	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
> +		 &dac8854_powerdown_mode_enum),
> +	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
> +	{ },
> +};
> +
> +#define DAC8554_CHANNEL(chan) { \
> +	.type = IIO_VOLTAGE, \
> +	.indexed = 1, \
> +	.output = 1, \
> +	.channel = (chan), \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +	.address = (chan), \
> +	.ext_info = dac8554_ext_info, \
> +}
> +static const struct iio_chan_spec dac8554_channels[] = {
> +	DAC8554_CHANNEL(0),
> +	DAC8554_CHANNEL(1),
> +	DAC8554_CHANNEL(2),
> +	DAC8554_CHANNEL(3),
> +};
> +#undef DAC8554_CHANNEL
> +
> +static int dac8554_probe(struct spi_device *spi)
> +{
> +	struct dac8554_state *st;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	u32 addr;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->dev.parent = &spi->dev;
> +	indio_dev->name = DAC8554_DRIVERNAME;
> +	indio_dev->info = &dac8554_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = dac8554_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
> +
> +	spi_set_drvdata(spi, indio_dev);
> +
> +	st = iio_priv(indio_dev);
> +
> +	if (!spi->dev.of_node) {
> +		dev_err(&spi->dev, "missing OF node");
> +		return -EINVAL;
> +	}
> +	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
> +	if (ret || addr < 0 || addr > 2) {
> +		dev_err(&spi->dev, "no or invalid chip address");
> +		return -EINVAL;
> +	}
> +
> +	st->spi = spi;
> +	st->addr = addr;
> +
> +	st->reg = devm_regulator_get(&spi->dev, "vref");
> +	if (IS_ERR(st->reg))
> +		return PTR_ERR(st->reg);
> +
> +	ret = regulator_enable(st->reg);
> +	if (ret)
> +		return ret;
> +
> +	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	return 0;
> +
> +error_disable_reg:
> +	regulator_disable(st->reg);
> +	return ret;
> +}
> +
> +static int dac8554_remove(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	regulator_disable(st->reg);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dac8554_of_match[] = {
> +	{ .compatible = "ti,dac8554" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, dac8554_of_match);
> +
> +static struct spi_driver dac8554_driver = {
> +	.driver = {
> +		.name = DAC8554_DRIVERNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = dac8554_of_match,
> +	 },
> +	.probe = dac8554_probe,
> +	.remove = dac8554_remove,
> +};
> +module_spi_driver(dac8554_driver);
> +
> +MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz@avionic-design.de>");
> +MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
> +MODULE_LICENSE("GPL v2");
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] iio: add driver for the TI DAC8554
@ 2014-12-18 21:30     ` Hartmut Knaack
  0 siblings, 0 replies; 12+ messages in thread
From: Hartmut Knaack @ 2014-12-18 21:30 UTC (permalink / raw)
  To: Nikolaus Schulz, linux-iio-u79uwXL29TY76Z2rM5mHXA,
	Jonathan Cameron, Lars-Peter Clausen, Peter Meerwald,
	Grant Likely, Rob Herring, Michael Welling, Philippe Reynes,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Alban Bedel

Nikolaus Schulz schrieb am 15.12.2014 um 12:39:
> The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
> interface.
> 
> Changes in v3:
> * Small fixes in the documentation of struct dac8554_state
> * Replace some magic constants with macros
> * Replace memset on powerdown state arrays with explicit loop
> * If probing fails due to invalid DT data, return -EINVAL instead of
>   -ENODEV
> * Add driver dependency on regulator support
> * Move regulator_get_voltage call from probe time to dac8554_read_raw
> * Rename _(read|write)_raw argument "mask" to "info"
> * Make iio_chan_spec variable static
> * DT: add vendor prefix to device specific "address" property
> * Whitespace fixes
> 
> Changes in v2:
> * Use DMA-safe buffer for SPI transfer
> * Normalize powerdown_mode name "hi-z" to "three_state" as per
>   ABI/testing/sysfs-bus-iio
> * Register device late in probe function
> * Avoid powerdown broadcast update, which touches all DAC on the bus
> 
> Signed-off-by: Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
Reviewed-by: Hartmut Knaack <knaack.h-Mmb7MZpHnFY@public.gmane.org>
> ---
>  drivers/iio/dac/Kconfig      |  11 ++
>  drivers/iio/dac/Makefile     |   1 +
>  drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 393 insertions(+)
>  create mode 100644 drivers/iio/dac/ti-dac8554.c
> 
> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
> index 2236ea2..d3b21c853b5 100644
> --- a/drivers/iio/dac/Kconfig
> +++ b/drivers/iio/dac/Kconfig
> @@ -181,4 +181,15 @@ config MCP4922
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called mcp4922.
>  
> +config TI_DAC8554
> +	tristate "TI DAC8554 driver"
> +	depends on SPI
> +	depends on OF
> +	depends on REGULATOR
> +	help
> +	  Say yes here to build the driver for the TI DAC8554.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called ti-dac8554.
> +
>  endmenu
> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
> index 52be7e1..b98d79a 100644
> --- a/drivers/iio/dac/Makefile
> +++ b/drivers/iio/dac/Makefile
> @@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
>  obj-$(CONFIG_MAX5821) += max5821.o
>  obj-$(CONFIG_MCP4725) += mcp4725.o
>  obj-$(CONFIG_MCP4922) += mcp4922.o
> +obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
> diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
> new file mode 100644
> index 0000000..84b9f42
> --- /dev/null
> +++ b/drivers/iio/dac/ti-dac8554.c
> @@ -0,0 +1,381 @@
> +/*
> + * TI DAC8554 Digital to Analog Converter SPI driver
> + *
> + * Copyright (C) 2014 Avionic Design GmbH
> + *
> + * Based on ad5446r_spi.c
> + * Copyright (C) 2010,2011 Analog Devices Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/spi/spi.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/module.h>
> +#include <linux/iio/iio.h>
> +#include <linux/of.h>
> +
> +#define DAC8554_DRIVERNAME			"ti-dac8554"
> +#define DAC8554_DAC_CHANNELS			4
> +#define DAC8554_DATABITS			16
> +
> +/* Load commands */
> +#define DAC8554_CMD_STORE_DAC_N			0x0
> +#define DAC8554_CMD_UPDATE_DAC_N		0x1
> +#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
> +#define DAC8554_CMD_UPDATE_BROADCAST		0x3
> +
> +#define DAC8554_BROADCAST_USE_SRDATA		0x2
> +
> +/* Powerdown modes (PD1 | PD2 bits) */
> +#define DAC8554_PWRDN_HIZ			0x0
> +#define DAC8554_PWRDN_1K			0x1
> +#define DAC8554_PWRDN_100K			0x2
> +
> +/* Input shift register composition */
> +#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
> +#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
> +#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
> +#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
> +
> +/**
> + * struct dac8554_state - driver instance specific data
> + * @spi:		SPI device
> + * @reg:		supply regulator
> + * @addr:		two-bit chip address
> + * @val:		channel data
> + * @powerdown:		channel powerdown flag
> + * @powerdown_mode:	channel powerdown mode
> + * @xfer:		SPI transfer buffer
> + */
> +struct dac8554_state {
> +	struct spi_device		*spi;
> +	struct regulator		*reg;
> +	unsigned			addr;
> +	u16				val[DAC8554_DAC_CHANNELS];
> +	bool				powerdown[DAC8554_DAC_CHANNELS];
> +	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
> +
> +	/*
> +	 * DMA (thus cache coherency maintenance) requires the
> +	 * transfer buffers to live in their own cache lines.
> +	 */
> +	u8				xfer[3] ____cacheline_aligned;
> +};
> +
> +static int dac8554_spi_write(struct dac8554_state *st,
> +			     unsigned cmd,
> +			     unsigned chan_addr,
> +			     unsigned val)
> +{
> +	u32 data;
> +
> +	/*
> +	 * The input shift register is 24 bits wide. The 8 MSB are
> +	 * control bits, followed by 16 data bits.
> +	 * The first two bits A1 and A0 address a DAC8554 chip.
> +	 * The next two are the command bits, LD1 and LD0.
> +	 * After a don't-care-bit, the next two bits select the channel.
> +	 * The final control bit PD0 is a flag signalling if the data
> +	 * bits encode a powerdown mode. We merge PD0 with the adjacent
> +	 * data bits.
> +	 */
> +
> +	if (cmd > 3 || chan_addr > 3 ||
> +			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
> +		return -EINVAL;
> +
> +	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
> +	       DAC8554_CHAN_TO_SR(chan_addr) | val;
> +
> +	st->xfer[0] = data >> 16;
> +	st->xfer[1] = data >> 8;
> +	st->xfer[2] = data;
> +
> +	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
> +}
> +
> +static int dac8554_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val,
> +			    int *val2,
> +			    long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int voltage_uv;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		*val = st->val[chan->address];
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		voltage_uv = regulator_get_voltage(st->reg);
> +		if (voltage_uv < 0)
> +			return voltage_uv;
> +		*val = voltage_uv / 1000;
> +		*val2 = DAC8554_DATABITS;
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int dac8554_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val,
> +			     int val2,
> +			     long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int err;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (val > 0xffff || val < 0)
> +			return -EINVAL;
> +
> +		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address, val);
> +		if (err)
> +			return err;
> +
> +		st->val[chan->address] = val;
> +
> +		/* By hw design, DAC updates automatically trigger powerup. */
> +		st->powerdown[chan->address] = false;
> +
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return st->powerdown_mode[chan->address];
> +}
> +
> +static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan,
> +				      unsigned int mode)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	st->powerdown_mode[chan->address] = mode;
> +
> +	return 0;
> +}
> +
> +static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
> +					  uintptr_t private,
> +					  const struct iio_chan_spec *chan,
> +					  char *buf)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
> +}
> +
> +static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
> +					   uintptr_t private,
> +					   const struct iio_chan_spec *chan,
> +					   const char *buf,
> +					   size_t len)
> +{
> +	bool powerdown;
> +	int ret;
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	u8 powerdown_mode;
> +
> +	ret = strtobool(buf, &powerdown);
> +	if (ret)
> +		return ret;
> +
> +	st->powerdown[chan->address] = powerdown;
> +
> +	if (powerdown) {
> +		powerdown_mode = st->powerdown_mode[chan->address];
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +	} else {
> +		/* Load DAC with cached value. This triggers a powerup. */
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					st->val[chan->address]);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int dac8554_powerdown(struct dac8554_state *st,
> +			     u8 powerdown_mode)
> +{
> +	int chan, cmd, ret;
> +
> +	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
> +		cmd = chan ? DAC8554_CMD_STORE_DAC_N
> +			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
> +		ret = dac8554_spi_write(st, cmd, chan,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
> +		st->powerdown_mode[chan] = powerdown_mode;
> +		st->powerdown[chan] = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct iio_info dac8554_info = {
> +	.write_raw = dac8554_write_raw,
> +	.read_raw = dac8554_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static const char * const dac8554_powerdown_modes[] = {
> +	"three_state",
> +	"1kohm_to_gnd",
> +	"100kohm_to_gnd"
> +};
> +
> +static const struct iio_enum dac8854_powerdown_mode_enum = {
> +	.items = dac8554_powerdown_modes,
> +	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
> +	.get = dac8554_get_powerdown_mode,
> +	.set = dac8554_set_powerdown_mode,
> +};
> +
> +static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
> +	{
> +		.name = "powerdown",
> +		.read = dac8554_read_dac_powerdown,
> +		.write = dac8554_write_dac_powerdown,
> +		.shared = IIO_SEPARATE,
> +	},
> +	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
> +		 &dac8854_powerdown_mode_enum),
> +	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
> +	{ },
> +};
> +
> +#define DAC8554_CHANNEL(chan) { \
> +	.type = IIO_VOLTAGE, \
> +	.indexed = 1, \
> +	.output = 1, \
> +	.channel = (chan), \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +	.address = (chan), \
> +	.ext_info = dac8554_ext_info, \
> +}
> +static const struct iio_chan_spec dac8554_channels[] = {
> +	DAC8554_CHANNEL(0),
> +	DAC8554_CHANNEL(1),
> +	DAC8554_CHANNEL(2),
> +	DAC8554_CHANNEL(3),
> +};
> +#undef DAC8554_CHANNEL
> +
> +static int dac8554_probe(struct spi_device *spi)
> +{
> +	struct dac8554_state *st;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	u32 addr;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->dev.parent = &spi->dev;
> +	indio_dev->name = DAC8554_DRIVERNAME;
> +	indio_dev->info = &dac8554_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = dac8554_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
> +
> +	spi_set_drvdata(spi, indio_dev);
> +
> +	st = iio_priv(indio_dev);
> +
> +	if (!spi->dev.of_node) {
> +		dev_err(&spi->dev, "missing OF node");
> +		return -EINVAL;
> +	}
> +	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
> +	if (ret || addr < 0 || addr > 2) {
> +		dev_err(&spi->dev, "no or invalid chip address");
> +		return -EINVAL;
> +	}
> +
> +	st->spi = spi;
> +	st->addr = addr;
> +
> +	st->reg = devm_regulator_get(&spi->dev, "vref");
> +	if (IS_ERR(st->reg))
> +		return PTR_ERR(st->reg);
> +
> +	ret = regulator_enable(st->reg);
> +	if (ret)
> +		return ret;
> +
> +	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	return 0;
> +
> +error_disable_reg:
> +	regulator_disable(st->reg);
> +	return ret;
> +}
> +
> +static int dac8554_remove(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	regulator_disable(st->reg);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dac8554_of_match[] = {
> +	{ .compatible = "ti,dac8554" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, dac8554_of_match);
> +
> +static struct spi_driver dac8554_driver = {
> +	.driver = {
> +		.name = DAC8554_DRIVERNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = dac8554_of_match,
> +	 },
> +	.probe = dac8554_probe,
> +	.remove = dac8554_remove,
> +};
> +module_spi_driver(dac8554_driver);
> +
> +MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>");
> +MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
> +MODULE_LICENSE("GPL v2");
> 

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] iio: add driver for the TI DAC8554
  2014-12-15 11:39 ` [PATCH v3 2/2] iio: add driver for the TI DAC8554 Nikolaus Schulz
  2014-12-18 21:30     ` Hartmut Knaack
@ 2014-12-26 11:51   ` Jonathan Cameron
  2014-12-26 16:10       ` Hartmut Knaack
  1 sibling, 1 reply; 12+ messages in thread
From: Jonathan Cameron @ 2014-12-26 11:51 UTC (permalink / raw)
  To: Nikolaus Schulz, linux-iio, Hartmut Knaack, Lars-Peter Clausen,
	Peter Meerwald, Grant Likely, Rob Herring, Michael Welling,
	Philippe Reynes, linux-kernel, devicetree
  Cc: Alban Bedel

On 15/12/14 11:39, Nikolaus Schulz wrote:
> The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
> interface.
> 
> Changes in v3:
> * Small fixes in the documentation of struct dac8554_state
> * Replace some magic constants with macros
> * Replace memset on powerdown state arrays with explicit loop
> * If probing fails due to invalid DT data, return -EINVAL instead of
>   -ENODEV
> * Add driver dependency on regulator support
> * Move regulator_get_voltage call from probe time to dac8554_read_raw
> * Rename _(read|write)_raw argument "mask" to "info"
> * Make iio_chan_spec variable static
> * DT: add vendor prefix to device specific "address" property
> * Whitespace fixes
> 
> Changes in v2:
> * Use DMA-safe buffer for SPI transfer
> * Normalize powerdown_mode name "hi-z" to "three_state" as per
>   ABI/testing/sysfs-bus-iio
> * Register device late in probe function
> * Avoid powerdown broadcast update, which touches all DAC on the bus
> 
> Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
A slightly inconsistency inline (you only allow for chip addresses up
to 2 rather than 3)

Otherwise, looks good - though we'll want Mark to say if he is fine with
the addressing bit of the device tree file and you to drop the line at
the top as asked for.

Thanks,

Jonathan
> ---
>  drivers/iio/dac/Kconfig      |  11 ++
>  drivers/iio/dac/Makefile     |   1 +
>  drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 393 insertions(+)
>  create mode 100644 drivers/iio/dac/ti-dac8554.c
> 
> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
> index 2236ea2..d3b21c853b5 100644
> --- a/drivers/iio/dac/Kconfig
> +++ b/drivers/iio/dac/Kconfig
> @@ -181,4 +181,15 @@ config MCP4922
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called mcp4922.
>  
> +config TI_DAC8554
> +	tristate "TI DAC8554 driver"
> +	depends on SPI
> +	depends on OF
> +	depends on REGULATOR
> +	help
> +	  Say yes here to build the driver for the TI DAC8554.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called ti-dac8554.
> +
>  endmenu
> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
> index 52be7e1..b98d79a 100644
> --- a/drivers/iio/dac/Makefile
> +++ b/drivers/iio/dac/Makefile
> @@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
>  obj-$(CONFIG_MAX5821) += max5821.o
>  obj-$(CONFIG_MCP4725) += mcp4725.o
>  obj-$(CONFIG_MCP4922) += mcp4922.o
> +obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
> diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
> new file mode 100644
> index 0000000..84b9f42
> --- /dev/null
> +++ b/drivers/iio/dac/ti-dac8554.c
> @@ -0,0 +1,381 @@
> +/*
> + * TI DAC8554 Digital to Analog Converter SPI driver
> + *
> + * Copyright (C) 2014 Avionic Design GmbH
> + *
> + * Based on ad5446r_spi.c
> + * Copyright (C) 2010,2011 Analog Devices Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/spi/spi.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/module.h>
> +#include <linux/iio/iio.h>
> +#include <linux/of.h>
> +
> +#define DAC8554_DRIVERNAME			"ti-dac8554"
> +#define DAC8554_DAC_CHANNELS			4
> +#define DAC8554_DATABITS			16
> +
> +/* Load commands */
> +#define DAC8554_CMD_STORE_DAC_N			0x0
> +#define DAC8554_CMD_UPDATE_DAC_N		0x1
> +#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
> +#define DAC8554_CMD_UPDATE_BROADCAST		0x3
> +
> +#define DAC8554_BROADCAST_USE_SRDATA		0x2
> +
> +/* Powerdown modes (PD1 | PD2 bits) */
> +#define DAC8554_PWRDN_HIZ			0x0
> +#define DAC8554_PWRDN_1K			0x1
> +#define DAC8554_PWRDN_100K			0x2
> +
> +/* Input shift register composition */
> +#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
> +#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
> +#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
> +#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
> +
> +/**
> + * struct dac8554_state - driver instance specific data
> + * @spi:		SPI device
> + * @reg:		supply regulator
> + * @addr:		two-bit chip address
> + * @val:		channel data
> + * @powerdown:		channel powerdown flag
> + * @powerdown_mode:	channel powerdown mode
> + * @xfer:		SPI transfer buffer
> + */
> +struct dac8554_state {
> +	struct spi_device		*spi;
> +	struct regulator		*reg;
> +	unsigned			addr;
> +	u16				val[DAC8554_DAC_CHANNELS];
> +	bool				powerdown[DAC8554_DAC_CHANNELS];
> +	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
> +
> +	/*
> +	 * DMA (thus cache coherency maintenance) requires the
> +	 * transfer buffers to live in their own cache lines.
> +	 */
> +	u8				xfer[3] ____cacheline_aligned;
> +};
> +
> +static int dac8554_spi_write(struct dac8554_state *st,
> +			     unsigned cmd,
> +			     unsigned chan_addr,
> +			     unsigned val)
> +{
> +	u32 data;
> +
> +	/*
> +	 * The input shift register is 24 bits wide. The 8 MSB are
> +	 * control bits, followed by 16 data bits.
> +	 * The first two bits A1 and A0 address a DAC8554 chip.
> +	 * The next two are the command bits, LD1 and LD0.
> +	 * After a don't-care-bit, the next two bits select the channel.
> +	 * The final control bit PD0 is a flag signalling if the data
> +	 * bits encode a powerdown mode. We merge PD0 with the adjacent
> +	 * data bits.
> +	 */
> +
> +	if (cmd > 3 || chan_addr > 3 ||
> +			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
> +		return -EINVAL;
> +
> +	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
> +	       DAC8554_CHAN_TO_SR(chan_addr) | val;
> +
> +	st->xfer[0] = data >> 16;
> +	st->xfer[1] = data >> 8;
> +	st->xfer[2] = data;
> +
> +	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
> +}
> +
> +static int dac8554_read_raw(struct iio_dev *indio_dev,
> +			    struct iio_chan_spec const *chan,
> +			    int *val,
> +			    int *val2,
> +			    long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int voltage_uv;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		*val = st->val[chan->address];
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		voltage_uv = regulator_get_voltage(st->reg);
> +		if (voltage_uv < 0)
> +			return voltage_uv;
> +		*val = voltage_uv / 1000;
> +		*val2 = DAC8554_DATABITS;
> +		return IIO_VAL_FRACTIONAL_LOG2;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int dac8554_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val,
> +			     int val2,
> +			     long info)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	int err;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		if (val > 0xffff || val < 0)
> +			return -EINVAL;
> +
> +		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address, val);
> +		if (err)
> +			return err;
> +
> +		st->val[chan->address] = val;
> +
> +		/* By hw design, DAC updates automatically trigger powerup. */
> +		st->powerdown[chan->address] = false;
> +
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return st->powerdown_mode[chan->address];
> +}
> +
> +static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
> +				      const struct iio_chan_spec *chan,
> +				      unsigned int mode)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	st->powerdown_mode[chan->address] = mode;
> +
> +	return 0;
> +}
> +
> +static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
> +					  uintptr_t private,
> +					  const struct iio_chan_spec *chan,
> +					  char *buf)
> +{
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
> +}
> +
> +static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
> +					   uintptr_t private,
> +					   const struct iio_chan_spec *chan,
> +					   const char *buf,
> +					   size_t len)
> +{
> +	bool powerdown;
> +	int ret;
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +	u8 powerdown_mode;
> +
> +	ret = strtobool(buf, &powerdown);
> +	if (ret)
> +		return ret;
> +
> +	st->powerdown[chan->address] = powerdown;
> +
> +	if (powerdown) {
> +		powerdown_mode = st->powerdown_mode[chan->address];
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +	} else {
> +		/* Load DAC with cached value. This triggers a powerup. */
> +		ret = dac8554_spi_write(st,
> +					DAC8554_CMD_UPDATE_DAC_N,
> +					chan->address,
> +					st->val[chan->address]);
> +	}
> +
> +	if (ret)
> +		return ret;
> +
> +	return len;
> +}
> +
> +static int dac8554_powerdown(struct dac8554_state *st,
> +			     u8 powerdown_mode)
> +{
> +	int chan, cmd, ret;
> +
> +	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
> +		cmd = chan ? DAC8554_CMD_STORE_DAC_N
> +			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
> +		ret = dac8554_spi_write(st, cmd, chan,
> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
> +		if (ret)
> +			return ret;
> +	}
> +
> +	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
> +		st->powerdown_mode[chan] = powerdown_mode;
> +		st->powerdown[chan] = true;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct iio_info dac8554_info = {
> +	.write_raw = dac8554_write_raw,
> +	.read_raw = dac8554_read_raw,
> +	.driver_module = THIS_MODULE,
> +};
> +
> +static const char * const dac8554_powerdown_modes[] = {
> +	"three_state",
> +	"1kohm_to_gnd",
> +	"100kohm_to_gnd"
> +};
> +
> +static const struct iio_enum dac8854_powerdown_mode_enum = {
> +	.items = dac8554_powerdown_modes,
> +	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
> +	.get = dac8554_get_powerdown_mode,
> +	.set = dac8554_set_powerdown_mode,
> +};
> +
> +static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
> +	{
> +		.name = "powerdown",
> +		.read = dac8554_read_dac_powerdown,
> +		.write = dac8554_write_dac_powerdown,
> +		.shared = IIO_SEPARATE,
> +	},
> +	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
> +		 &dac8854_powerdown_mode_enum),
> +	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
> +	{ },
> +};
> +
> +#define DAC8554_CHANNEL(chan) { \
> +	.type = IIO_VOLTAGE, \
> +	.indexed = 1, \
> +	.output = 1, \
> +	.channel = (chan), \
> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
> +	.address = (chan), \
> +	.ext_info = dac8554_ext_info, \
> +}
> +static const struct iio_chan_spec dac8554_channels[] = {
> +	DAC8554_CHANNEL(0),
> +	DAC8554_CHANNEL(1),
> +	DAC8554_CHANNEL(2),
> +	DAC8554_CHANNEL(3),
> +};
> +#undef DAC8554_CHANNEL
> +
> +static int dac8554_probe(struct spi_device *spi)
> +{
> +	struct dac8554_state *st;
> +	struct iio_dev *indio_dev;
> +	int ret;
> +	u32 addr;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	indio_dev->dev.parent = &spi->dev;
> +	indio_dev->name = DAC8554_DRIVERNAME;
> +	indio_dev->info = &dac8554_info;
> +	indio_dev->modes = INDIO_DIRECT_MODE;
> +	indio_dev->channels = dac8554_channels;
> +	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
> +
> +	spi_set_drvdata(spi, indio_dev);
> +
> +	st = iio_priv(indio_dev);
> +
> +	if (!spi->dev.of_node) {
> +		dev_err(&spi->dev, "missing OF node");
> +		return -EINVAL;
> +	}
> +	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
> +	if (ret || addr < 0 || addr > 2) {
In your reply to Mark, you said the address allows 4 chips.  Here you
limit it at 0,1,2 giving 3 devices.  Should this be > 3?

> +		dev_err(&spi->dev, "no or invalid chip address");
> +		return -EINVAL;
> +	}
> +
> +	st->spi = spi;
> +	st->addr = addr;
> +
> +	st->reg = devm_regulator_get(&spi->dev, "vref");
> +	if (IS_ERR(st->reg))
> +		return PTR_ERR(st->reg);
> +
> +	ret = regulator_enable(st->reg);
> +	if (ret)
> +		return ret;
> +
> +	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	ret = iio_device_register(indio_dev);
> +	if (ret)
> +		goto error_disable_reg;
> +
> +	return 0;
> +
> +error_disable_reg:
> +	regulator_disable(st->reg);
> +	return ret;
> +}
> +
> +static int dac8554_remove(struct spi_device *spi)
> +{
> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
> +	struct dac8554_state *st = iio_priv(indio_dev);
> +
> +	iio_device_unregister(indio_dev);
> +	regulator_disable(st->reg);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dac8554_of_match[] = {
> +	{ .compatible = "ti,dac8554" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, dac8554_of_match);
> +
> +static struct spi_driver dac8554_driver = {
> +	.driver = {
> +		.name = DAC8554_DRIVERNAME,
> +		.owner = THIS_MODULE,
> +		.of_match_table = dac8554_of_match,
> +	 },
> +	.probe = dac8554_probe,
> +	.remove = dac8554_remove,
> +};
> +module_spi_driver(dac8554_driver);
> +
> +MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz@avionic-design.de>");
> +MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
> +MODULE_LICENSE("GPL v2");
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] iio: add driver for the TI DAC8554
@ 2014-12-26 16:10       ` Hartmut Knaack
  0 siblings, 0 replies; 12+ messages in thread
From: Hartmut Knaack @ 2014-12-26 16:10 UTC (permalink / raw)
  To: Jonathan Cameron, Nikolaus Schulz, linux-iio, Lars-Peter Clausen,
	Peter Meerwald, Grant Likely, Rob Herring, Michael Welling,
	Philippe Reynes, linux-kernel, devicetree
  Cc: Alban Bedel

Jonathan Cameron schrieb am 26.12.2014 um 12:51:
> On 15/12/14 11:39, Nikolaus Schulz wrote:
>> The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
>> interface.
>>
>> Changes in v3:
>> * Small fixes in the documentation of struct dac8554_state
>> * Replace some magic constants with macros
>> * Replace memset on powerdown state arrays with explicit loop
>> * If probing fails due to invalid DT data, return -EINVAL instead of
>>   -ENODEV
>> * Add driver dependency on regulator support
>> * Move regulator_get_voltage call from probe time to dac8554_read_raw
>> * Rename _(read|write)_raw argument "mask" to "info"
>> * Make iio_chan_spec variable static
>> * DT: add vendor prefix to device specific "address" property
>> * Whitespace fixes
>>
>> Changes in v2:
>> * Use DMA-safe buffer for SPI transfer
>> * Normalize powerdown_mode name "hi-z" to "three_state" as per
>>   ABI/testing/sysfs-bus-iio
>> * Register device late in probe function
>> * Avoid powerdown broadcast update, which touches all DAC on the bus
>>
>> Signed-off-by: Nikolaus Schulz <nikolaus.schulz@avionic-design.de>
> A slightly inconsistency inline (you only allow for chip addresses up
> to 2 rather than 3)
> 
> Otherwise, looks good - though we'll want Mark to say if he is fine with
> the addressing bit of the device tree file and you to drop the line at
> the top as asked for.
> 
> Thanks,
> 
> Jonathan
>> ---
>>  drivers/iio/dac/Kconfig      |  11 ++
>>  drivers/iio/dac/Makefile     |   1 +
>>  drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 393 insertions(+)
>>  create mode 100644 drivers/iio/dac/ti-dac8554.c
>>
>> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
>> index 2236ea2..d3b21c853b5 100644
>> --- a/drivers/iio/dac/Kconfig
>> +++ b/drivers/iio/dac/Kconfig
>> @@ -181,4 +181,15 @@ config MCP4922
>>  	  To compile this driver as a module, choose M here: the module
>>  	  will be called mcp4922.
>>  
>> +config TI_DAC8554
>> +	tristate "TI DAC8554 driver"
>> +	depends on SPI
>> +	depends on OF
>> +	depends on REGULATOR
>> +	help
>> +	  Say yes here to build the driver for the TI DAC8554.
>> +
>> +	  To compile this driver as a module, choose M here: the module
>> +	  will be called ti-dac8554.
>> +
>>  endmenu
>> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
>> index 52be7e1..b98d79a 100644
>> --- a/drivers/iio/dac/Makefile
>> +++ b/drivers/iio/dac/Makefile
>> @@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
>>  obj-$(CONFIG_MAX5821) += max5821.o
>>  obj-$(CONFIG_MCP4725) += mcp4725.o
>>  obj-$(CONFIG_MCP4922) += mcp4922.o
>> +obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
>> diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
>> new file mode 100644
>> index 0000000..84b9f42
>> --- /dev/null
>> +++ b/drivers/iio/dac/ti-dac8554.c
>> @@ -0,0 +1,381 @@
>> +/*
>> + * TI DAC8554 Digital to Analog Converter SPI driver
>> + *
>> + * Copyright (C) 2014 Avionic Design GmbH
>> + *
>> + * Based on ad5446r_spi.c
>> + * Copyright (C) 2010,2011 Analog Devices Inc.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <linux/spi/spi.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/module.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/of.h>
>> +
>> +#define DAC8554_DRIVERNAME			"ti-dac8554"
>> +#define DAC8554_DAC_CHANNELS			4
>> +#define DAC8554_DATABITS			16
>> +
>> +/* Load commands */
>> +#define DAC8554_CMD_STORE_DAC_N			0x0
>> +#define DAC8554_CMD_UPDATE_DAC_N		0x1
>> +#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
>> +#define DAC8554_CMD_UPDATE_BROADCAST		0x3
>> +
>> +#define DAC8554_BROADCAST_USE_SRDATA		0x2
>> +
>> +/* Powerdown modes (PD1 | PD2 bits) */
>> +#define DAC8554_PWRDN_HIZ			0x0
>> +#define DAC8554_PWRDN_1K			0x1
>> +#define DAC8554_PWRDN_100K			0x2
>> +
>> +/* Input shift register composition */
>> +#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
>> +#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
>> +#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
>> +#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
>> +
>> +/**
>> + * struct dac8554_state - driver instance specific data
>> + * @spi:		SPI device
>> + * @reg:		supply regulator
>> + * @addr:		two-bit chip address
>> + * @val:		channel data
>> + * @powerdown:		channel powerdown flag
>> + * @powerdown_mode:	channel powerdown mode
>> + * @xfer:		SPI transfer buffer
>> + */
>> +struct dac8554_state {
>> +	struct spi_device		*spi;
>> +	struct regulator		*reg;
>> +	unsigned			addr;
>> +	u16				val[DAC8554_DAC_CHANNELS];
>> +	bool				powerdown[DAC8554_DAC_CHANNELS];
>> +	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
>> +
>> +	/*
>> +	 * DMA (thus cache coherency maintenance) requires the
>> +	 * transfer buffers to live in their own cache lines.
>> +	 */
>> +	u8				xfer[3] ____cacheline_aligned;
>> +};
>> +
>> +static int dac8554_spi_write(struct dac8554_state *st,
>> +			     unsigned cmd,
>> +			     unsigned chan_addr,
>> +			     unsigned val)
>> +{
>> +	u32 data;
>> +
>> +	/*
>> +	 * The input shift register is 24 bits wide. The 8 MSB are
>> +	 * control bits, followed by 16 data bits.
>> +	 * The first two bits A1 and A0 address a DAC8554 chip.
>> +	 * The next two are the command bits, LD1 and LD0.
>> +	 * After a don't-care-bit, the next two bits select the channel.
>> +	 * The final control bit PD0 is a flag signalling if the data
>> +	 * bits encode a powerdown mode. We merge PD0 with the adjacent
>> +	 * data bits.
>> +	 */
>> +
>> +	if (cmd > 3 || chan_addr > 3 ||
>> +			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
>> +		return -EINVAL;
>> +
>> +	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
>> +	       DAC8554_CHAN_TO_SR(chan_addr) | val;
>> +
>> +	st->xfer[0] = data >> 16;
>> +	st->xfer[1] = data >> 8;
>> +	st->xfer[2] = data;
>> +
>> +	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
>> +}
>> +
>> +static int dac8554_read_raw(struct iio_dev *indio_dev,
>> +			    struct iio_chan_spec const *chan,
>> +			    int *val,
>> +			    int *val2,
>> +			    long info)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	int voltage_uv;
>> +
>> +	switch (info) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		*val = st->val[chan->address];
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_SCALE:
>> +		voltage_uv = regulator_get_voltage(st->reg);
>> +		if (voltage_uv < 0)
>> +			return voltage_uv;
>> +		*val = voltage_uv / 1000;
>> +		*val2 = DAC8554_DATABITS;
>> +		return IIO_VAL_FRACTIONAL_LOG2;
>> +	}
>> +	return -EINVAL;
>> +}
>> +
>> +static int dac8554_write_raw(struct iio_dev *indio_dev,
>> +			     struct iio_chan_spec const *chan,
>> +			     int val,
>> +			     int val2,
>> +			     long info)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	int err;
>> +
>> +	switch (info) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		if (val > 0xffff || val < 0)
>> +			return -EINVAL;
>> +
>> +		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address, val);
>> +		if (err)
>> +			return err;
>> +
>> +		st->val[chan->address] = val;
>> +
>> +		/* By hw design, DAC updates automatically trigger powerup. */
>> +		st->powerdown[chan->address] = false;
>> +
>> +		return 0;
>> +
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +}
>> +
>> +static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
>> +				      const struct iio_chan_spec *chan)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	return st->powerdown_mode[chan->address];
>> +}
>> +
>> +static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
>> +				      const struct iio_chan_spec *chan,
>> +				      unsigned int mode)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	st->powerdown_mode[chan->address] = mode;
>> +
>> +	return 0;
>> +}
>> +
>> +static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
>> +					  uintptr_t private,
>> +					  const struct iio_chan_spec *chan,
>> +					  char *buf)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
>> +}
>> +
>> +static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
>> +					   uintptr_t private,
>> +					   const struct iio_chan_spec *chan,
>> +					   const char *buf,
>> +					   size_t len)
>> +{
>> +	bool powerdown;
>> +	int ret;
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	u8 powerdown_mode;
>> +
>> +	ret = strtobool(buf, &powerdown);
>> +	if (ret)
>> +		return ret;
>> +
>> +	st->powerdown[chan->address] = powerdown;
>> +
>> +	if (powerdown) {
>> +		powerdown_mode = st->powerdown_mode[chan->address];
>> +		ret = dac8554_spi_write(st,
>> +					DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address,
>> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
>> +	} else {
>> +		/* Load DAC with cached value. This triggers a powerup. */
>> +		ret = dac8554_spi_write(st,
>> +					DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address,
>> +					st->val[chan->address]);
>> +	}
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	return len;
>> +}
>> +
>> +static int dac8554_powerdown(struct dac8554_state *st,
>> +			     u8 powerdown_mode)
>> +{
>> +	int chan, cmd, ret;
>> +
>> +	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
>> +		cmd = chan ? DAC8554_CMD_STORE_DAC_N
>> +			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
>> +		ret = dac8554_spi_write(st, cmd, chan,
>> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
>> +		st->powerdown_mode[chan] = powerdown_mode;
>> +		st->powerdown[chan] = true;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct iio_info dac8554_info = {
>> +	.write_raw = dac8554_write_raw,
>> +	.read_raw = dac8554_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +static const char * const dac8554_powerdown_modes[] = {
>> +	"three_state",
>> +	"1kohm_to_gnd",
>> +	"100kohm_to_gnd"
>> +};
>> +
>> +static const struct iio_enum dac8854_powerdown_mode_enum = {
>> +	.items = dac8554_powerdown_modes,
>> +	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
>> +	.get = dac8554_get_powerdown_mode,
>> +	.set = dac8554_set_powerdown_mode,
>> +};
>> +
>> +static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
>> +	{
>> +		.name = "powerdown",
>> +		.read = dac8554_read_dac_powerdown,
>> +		.write = dac8554_write_dac_powerdown,
>> +		.shared = IIO_SEPARATE,
>> +	},
>> +	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
>> +		 &dac8854_powerdown_mode_enum),
>> +	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
>> +	{ },
>> +};
>> +
>> +#define DAC8554_CHANNEL(chan) { \
>> +	.type = IIO_VOLTAGE, \
>> +	.indexed = 1, \
>> +	.output = 1, \
>> +	.channel = (chan), \
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>> +	.address = (chan), \
>> +	.ext_info = dac8554_ext_info, \
>> +}
>> +static const struct iio_chan_spec dac8554_channels[] = {
>> +	DAC8554_CHANNEL(0),
>> +	DAC8554_CHANNEL(1),
>> +	DAC8554_CHANNEL(2),
>> +	DAC8554_CHANNEL(3),
>> +};
>> +#undef DAC8554_CHANNEL
>> +
>> +static int dac8554_probe(struct spi_device *spi)
>> +{
>> +	struct dac8554_state *st;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +	u32 addr;
>> +
>> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
>> +	if (!indio_dev)
>> +		return -ENOMEM;
>> +
>> +	indio_dev->dev.parent = &spi->dev;
>> +	indio_dev->name = DAC8554_DRIVERNAME;
>> +	indio_dev->info = &dac8554_info;
>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>> +	indio_dev->channels = dac8554_channels;
>> +	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
>> +
>> +	spi_set_drvdata(spi, indio_dev);
>> +
>> +	st = iio_priv(indio_dev);
>> +
>> +	if (!spi->dev.of_node) {
>> +		dev_err(&spi->dev, "missing OF node");
>> +		return -EINVAL;
>> +	}
>> +	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
>> +	if (ret || addr < 0 || addr > 2) {
> In your reply to Mark, you said the address allows 4 chips.  Here you
> limit it at 0,1,2 giving 3 devices.  Should this be > 3?
> 
Good catch. The datasheet says that 4 devices are possible.
Btw. given that addr is unsigned, checking for values lower than 0 is not
really beneficial.

>> +		dev_err(&spi->dev, "no or invalid chip address");
>> +		return -EINVAL;
>> +	}
>> +
>> +	st->spi = spi;
>> +	st->addr = addr;
>> +
>> +	st->reg = devm_regulator_get(&spi->dev, "vref");
>> +	if (IS_ERR(st->reg))
>> +		return PTR_ERR(st->reg);
>> +
>> +	ret = regulator_enable(st->reg);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
>> +	if (ret)
>> +		goto error_disable_reg;
>> +
>> +	ret = iio_device_register(indio_dev);
>> +	if (ret)
>> +		goto error_disable_reg;
>> +
>> +	return 0;
>> +
>> +error_disable_reg:
>> +	regulator_disable(st->reg);
>> +	return ret;
>> +}
>> +
>> +static int dac8554_remove(struct spi_device *spi)
>> +{
>> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	iio_device_unregister(indio_dev);
>> +	regulator_disable(st->reg);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id dac8554_of_match[] = {
>> +	{ .compatible = "ti,dac8554" },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, dac8554_of_match);
>> +
>> +static struct spi_driver dac8554_driver = {
>> +	.driver = {
>> +		.name = DAC8554_DRIVERNAME,
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = dac8554_of_match,
>> +	 },
>> +	.probe = dac8554_probe,
>> +	.remove = dac8554_remove,
>> +};
>> +module_spi_driver(dac8554_driver);
>> +
>> +MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz@avionic-design.de>");
>> +MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
>> +MODULE_LICENSE("GPL v2");
>>
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v3 2/2] iio: add driver for the TI DAC8554
@ 2014-12-26 16:10       ` Hartmut Knaack
  0 siblings, 0 replies; 12+ messages in thread
From: Hartmut Knaack @ 2014-12-26 16:10 UTC (permalink / raw)
  To: Jonathan Cameron, Nikolaus Schulz,
	linux-iio-u79uwXL29TY76Z2rM5mHXA, Lars-Peter Clausen,
	Peter Meerwald, Grant Likely, Rob Herring, Michael Welling,
	Philippe Reynes, linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	devicetree-u79uwXL29TY76Z2rM5mHXA
  Cc: Alban Bedel

Jonathan Cameron schrieb am 26.12.2014 um 12:51:
> On 15/12/14 11:39, Nikolaus Schulz wrote:
>> The TI DAC8554 is a quad-channel Digital-to-Analog Converter with an SPI
>> interface.
>>
>> Changes in v3:
>> * Small fixes in the documentation of struct dac8554_state
>> * Replace some magic constants with macros
>> * Replace memset on powerdown state arrays with explicit loop
>> * If probing fails due to invalid DT data, return -EINVAL instead of
>>   -ENODEV
>> * Add driver dependency on regulator support
>> * Move regulator_get_voltage call from probe time to dac8554_read_raw
>> * Rename _(read|write)_raw argument "mask" to "info"
>> * Make iio_chan_spec variable static
>> * DT: add vendor prefix to device specific "address" property
>> * Whitespace fixes
>>
>> Changes in v2:
>> * Use DMA-safe buffer for SPI transfer
>> * Normalize powerdown_mode name "hi-z" to "three_state" as per
>>   ABI/testing/sysfs-bus-iio
>> * Register device late in probe function
>> * Avoid powerdown broadcast update, which touches all DAC on the bus
>>
>> Signed-off-by: Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>
> A slightly inconsistency inline (you only allow for chip addresses up
> to 2 rather than 3)
> 
> Otherwise, looks good - though we'll want Mark to say if he is fine with
> the addressing bit of the device tree file and you to drop the line at
> the top as asked for.
> 
> Thanks,
> 
> Jonathan
>> ---
>>  drivers/iio/dac/Kconfig      |  11 ++
>>  drivers/iio/dac/Makefile     |   1 +
>>  drivers/iio/dac/ti-dac8554.c | 381 +++++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 393 insertions(+)
>>  create mode 100644 drivers/iio/dac/ti-dac8554.c
>>
>> diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
>> index 2236ea2..d3b21c853b5 100644
>> --- a/drivers/iio/dac/Kconfig
>> +++ b/drivers/iio/dac/Kconfig
>> @@ -181,4 +181,15 @@ config MCP4922
>>  	  To compile this driver as a module, choose M here: the module
>>  	  will be called mcp4922.
>>  
>> +config TI_DAC8554
>> +	tristate "TI DAC8554 driver"
>> +	depends on SPI
>> +	depends on OF
>> +	depends on REGULATOR
>> +	help
>> +	  Say yes here to build the driver for the TI DAC8554.
>> +
>> +	  To compile this driver as a module, choose M here: the module
>> +	  will be called ti-dac8554.
>> +
>>  endmenu
>> diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
>> index 52be7e1..b98d79a 100644
>> --- a/drivers/iio/dac/Makefile
>> +++ b/drivers/iio/dac/Makefile
>> @@ -20,3 +20,4 @@ obj-$(CONFIG_MAX517) += max517.o
>>  obj-$(CONFIG_MAX5821) += max5821.o
>>  obj-$(CONFIG_MCP4725) += mcp4725.o
>>  obj-$(CONFIG_MCP4922) += mcp4922.o
>> +obj-$(CONFIG_TI_DAC8554) += ti-dac8554.o
>> diff --git a/drivers/iio/dac/ti-dac8554.c b/drivers/iio/dac/ti-dac8554.c
>> new file mode 100644
>> index 0000000..84b9f42
>> --- /dev/null
>> +++ b/drivers/iio/dac/ti-dac8554.c
>> @@ -0,0 +1,381 @@
>> +/*
>> + * TI DAC8554 Digital to Analog Converter SPI driver
>> + *
>> + * Copyright (C) 2014 Avionic Design GmbH
>> + *
>> + * Based on ad5446r_spi.c
>> + * Copyright (C) 2010,2011 Analog Devices Inc.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <linux/spi/spi.h>
>> +#include <linux/regulator/consumer.h>
>> +#include <linux/module.h>
>> +#include <linux/iio/iio.h>
>> +#include <linux/of.h>
>> +
>> +#define DAC8554_DRIVERNAME			"ti-dac8554"
>> +#define DAC8554_DAC_CHANNELS			4
>> +#define DAC8554_DATABITS			16
>> +
>> +/* Load commands */
>> +#define DAC8554_CMD_STORE_DAC_N			0x0
>> +#define DAC8554_CMD_UPDATE_DAC_N		0x1
>> +#define DAC8554_CMD_STORE_DAC_N_UPDATE_ALL	0x2
>> +#define DAC8554_CMD_UPDATE_BROADCAST		0x3
>> +
>> +#define DAC8554_BROADCAST_USE_SRDATA		0x2
>> +
>> +/* Powerdown modes (PD1 | PD2 bits) */
>> +#define DAC8554_PWRDN_HIZ			0x0
>> +#define DAC8554_PWRDN_1K			0x1
>> +#define DAC8554_PWRDN_100K			0x2
>> +
>> +/* Input shift register composition */
>> +#define DAC8554_ADDR_TO_SR(addr)		((addr) << 22)
>> +#define DAC8554_CMD_TO_SR(cmd)			((cmd) << 20)
>> +#define DAC8554_CHAN_TO_SR(chan)		((chan) << 17)
>> +#define DAC8554_PWRDN_TO_SR(mode)		(BIT(16) | (mode) << 14)
>> +
>> +/**
>> + * struct dac8554_state - driver instance specific data
>> + * @spi:		SPI device
>> + * @reg:		supply regulator
>> + * @addr:		two-bit chip address
>> + * @val:		channel data
>> + * @powerdown:		channel powerdown flag
>> + * @powerdown_mode:	channel powerdown mode
>> + * @xfer:		SPI transfer buffer
>> + */
>> +struct dac8554_state {
>> +	struct spi_device		*spi;
>> +	struct regulator		*reg;
>> +	unsigned			addr;
>> +	u16				val[DAC8554_DAC_CHANNELS];
>> +	bool				powerdown[DAC8554_DAC_CHANNELS];
>> +	u8				powerdown_mode[DAC8554_DAC_CHANNELS];
>> +
>> +	/*
>> +	 * DMA (thus cache coherency maintenance) requires the
>> +	 * transfer buffers to live in their own cache lines.
>> +	 */
>> +	u8				xfer[3] ____cacheline_aligned;
>> +};
>> +
>> +static int dac8554_spi_write(struct dac8554_state *st,
>> +			     unsigned cmd,
>> +			     unsigned chan_addr,
>> +			     unsigned val)
>> +{
>> +	u32 data;
>> +
>> +	/*
>> +	 * The input shift register is 24 bits wide. The 8 MSB are
>> +	 * control bits, followed by 16 data bits.
>> +	 * The first two bits A1 and A0 address a DAC8554 chip.
>> +	 * The next two are the command bits, LD1 and LD0.
>> +	 * After a don't-care-bit, the next two bits select the channel.
>> +	 * The final control bit PD0 is a flag signalling if the data
>> +	 * bits encode a powerdown mode. We merge PD0 with the adjacent
>> +	 * data bits.
>> +	 */
>> +
>> +	if (cmd > 3 || chan_addr > 3 ||
>> +			(val > 0xffff && (val & ~DAC8554_PWRDN_TO_SR(3))))
>> +		return -EINVAL;
>> +
>> +	data = DAC8554_ADDR_TO_SR(st->addr) | DAC8554_CMD_TO_SR(cmd) |
>> +	       DAC8554_CHAN_TO_SR(chan_addr) | val;
>> +
>> +	st->xfer[0] = data >> 16;
>> +	st->xfer[1] = data >> 8;
>> +	st->xfer[2] = data;
>> +
>> +	return spi_write(st->spi, st->xfer, sizeof(st->xfer));
>> +}
>> +
>> +static int dac8554_read_raw(struct iio_dev *indio_dev,
>> +			    struct iio_chan_spec const *chan,
>> +			    int *val,
>> +			    int *val2,
>> +			    long info)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	int voltage_uv;
>> +
>> +	switch (info) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		*val = st->val[chan->address];
>> +		return IIO_VAL_INT;
>> +	case IIO_CHAN_INFO_SCALE:
>> +		voltage_uv = regulator_get_voltage(st->reg);
>> +		if (voltage_uv < 0)
>> +			return voltage_uv;
>> +		*val = voltage_uv / 1000;
>> +		*val2 = DAC8554_DATABITS;
>> +		return IIO_VAL_FRACTIONAL_LOG2;
>> +	}
>> +	return -EINVAL;
>> +}
>> +
>> +static int dac8554_write_raw(struct iio_dev *indio_dev,
>> +			     struct iio_chan_spec const *chan,
>> +			     int val,
>> +			     int val2,
>> +			     long info)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	int err;
>> +
>> +	switch (info) {
>> +	case IIO_CHAN_INFO_RAW:
>> +		if (val > 0xffff || val < 0)
>> +			return -EINVAL;
>> +
>> +		err = dac8554_spi_write(st, DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address, val);
>> +		if (err)
>> +			return err;
>> +
>> +		st->val[chan->address] = val;
>> +
>> +		/* By hw design, DAC updates automatically trigger powerup. */
>> +		st->powerdown[chan->address] = false;
>> +
>> +		return 0;
>> +
>> +	default:
>> +		return -EINVAL;
>> +	}
>> +}
>> +
>> +static int dac8554_get_powerdown_mode(struct iio_dev *indio_dev,
>> +				      const struct iio_chan_spec *chan)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	return st->powerdown_mode[chan->address];
>> +}
>> +
>> +static int dac8554_set_powerdown_mode(struct iio_dev *indio_dev,
>> +				      const struct iio_chan_spec *chan,
>> +				      unsigned int mode)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	st->powerdown_mode[chan->address] = mode;
>> +
>> +	return 0;
>> +}
>> +
>> +static ssize_t dac8554_read_dac_powerdown(struct iio_dev *indio_dev,
>> +					  uintptr_t private,
>> +					  const struct iio_chan_spec *chan,
>> +					  char *buf)
>> +{
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	return sprintf(buf, "%d\n", st->powerdown[chan->address]);
>> +}
>> +
>> +static ssize_t dac8554_write_dac_powerdown(struct iio_dev *indio_dev,
>> +					   uintptr_t private,
>> +					   const struct iio_chan_spec *chan,
>> +					   const char *buf,
>> +					   size_t len)
>> +{
>> +	bool powerdown;
>> +	int ret;
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +	u8 powerdown_mode;
>> +
>> +	ret = strtobool(buf, &powerdown);
>> +	if (ret)
>> +		return ret;
>> +
>> +	st->powerdown[chan->address] = powerdown;
>> +
>> +	if (powerdown) {
>> +		powerdown_mode = st->powerdown_mode[chan->address];
>> +		ret = dac8554_spi_write(st,
>> +					DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address,
>> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
>> +	} else {
>> +		/* Load DAC with cached value. This triggers a powerup. */
>> +		ret = dac8554_spi_write(st,
>> +					DAC8554_CMD_UPDATE_DAC_N,
>> +					chan->address,
>> +					st->val[chan->address]);
>> +	}
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	return len;
>> +}
>> +
>> +static int dac8554_powerdown(struct dac8554_state *st,
>> +			     u8 powerdown_mode)
>> +{
>> +	int chan, cmd, ret;
>> +
>> +	for (chan = DAC8554_DAC_CHANNELS - 1; chan >= 0; --chan) {
>> +		cmd = chan ? DAC8554_CMD_STORE_DAC_N
>> +			   : DAC8554_CMD_STORE_DAC_N_UPDATE_ALL;
>> +		ret = dac8554_spi_write(st, cmd, chan,
>> +					DAC8554_PWRDN_TO_SR(powerdown_mode));
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	for (chan = 0; chan < DAC8554_DAC_CHANNELS; ++chan) {
>> +		st->powerdown_mode[chan] = powerdown_mode;
>> +		st->powerdown[chan] = true;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct iio_info dac8554_info = {
>> +	.write_raw = dac8554_write_raw,
>> +	.read_raw = dac8554_read_raw,
>> +	.driver_module = THIS_MODULE,
>> +};
>> +
>> +static const char * const dac8554_powerdown_modes[] = {
>> +	"three_state",
>> +	"1kohm_to_gnd",
>> +	"100kohm_to_gnd"
>> +};
>> +
>> +static const struct iio_enum dac8854_powerdown_mode_enum = {
>> +	.items = dac8554_powerdown_modes,
>> +	.num_items = ARRAY_SIZE(dac8554_powerdown_modes),
>> +	.get = dac8554_get_powerdown_mode,
>> +	.set = dac8554_set_powerdown_mode,
>> +};
>> +
>> +static const struct iio_chan_spec_ext_info dac8554_ext_info[] = {
>> +	{
>> +		.name = "powerdown",
>> +		.read = dac8554_read_dac_powerdown,
>> +		.write = dac8554_write_dac_powerdown,
>> +		.shared = IIO_SEPARATE,
>> +	},
>> +	IIO_ENUM("powerdown_mode", IIO_SEPARATE,
>> +		 &dac8854_powerdown_mode_enum),
>> +	IIO_ENUM_AVAILABLE("powerdown_mode", &dac8854_powerdown_mode_enum),
>> +	{ },
>> +};
>> +
>> +#define DAC8554_CHANNEL(chan) { \
>> +	.type = IIO_VOLTAGE, \
>> +	.indexed = 1, \
>> +	.output = 1, \
>> +	.channel = (chan), \
>> +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
>> +	.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
>> +	.address = (chan), \
>> +	.ext_info = dac8554_ext_info, \
>> +}
>> +static const struct iio_chan_spec dac8554_channels[] = {
>> +	DAC8554_CHANNEL(0),
>> +	DAC8554_CHANNEL(1),
>> +	DAC8554_CHANNEL(2),
>> +	DAC8554_CHANNEL(3),
>> +};
>> +#undef DAC8554_CHANNEL
>> +
>> +static int dac8554_probe(struct spi_device *spi)
>> +{
>> +	struct dac8554_state *st;
>> +	struct iio_dev *indio_dev;
>> +	int ret;
>> +	u32 addr;
>> +
>> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
>> +	if (!indio_dev)
>> +		return -ENOMEM;
>> +
>> +	indio_dev->dev.parent = &spi->dev;
>> +	indio_dev->name = DAC8554_DRIVERNAME;
>> +	indio_dev->info = &dac8554_info;
>> +	indio_dev->modes = INDIO_DIRECT_MODE;
>> +	indio_dev->channels = dac8554_channels;
>> +	indio_dev->num_channels = ARRAY_SIZE(dac8554_channels);
>> +
>> +	spi_set_drvdata(spi, indio_dev);
>> +
>> +	st = iio_priv(indio_dev);
>> +
>> +	if (!spi->dev.of_node) {
>> +		dev_err(&spi->dev, "missing OF node");
>> +		return -EINVAL;
>> +	}
>> +	ret = of_property_read_u32(spi->dev.of_node, "ti,address", &addr);
>> +	if (ret || addr < 0 || addr > 2) {
> In your reply to Mark, you said the address allows 4 chips.  Here you
> limit it at 0,1,2 giving 3 devices.  Should this be > 3?
> 
Good catch. The datasheet says that 4 devices are possible.
Btw. given that addr is unsigned, checking for values lower than 0 is not
really beneficial.

>> +		dev_err(&spi->dev, "no or invalid chip address");
>> +		return -EINVAL;
>> +	}
>> +
>> +	st->spi = spi;
>> +	st->addr = addr;
>> +
>> +	st->reg = devm_regulator_get(&spi->dev, "vref");
>> +	if (IS_ERR(st->reg))
>> +		return PTR_ERR(st->reg);
>> +
>> +	ret = regulator_enable(st->reg);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = dac8554_powerdown(st, DAC8554_PWRDN_100K);
>> +	if (ret)
>> +		goto error_disable_reg;
>> +
>> +	ret = iio_device_register(indio_dev);
>> +	if (ret)
>> +		goto error_disable_reg;
>> +
>> +	return 0;
>> +
>> +error_disable_reg:
>> +	regulator_disable(st->reg);
>> +	return ret;
>> +}
>> +
>> +static int dac8554_remove(struct spi_device *spi)
>> +{
>> +	struct iio_dev *indio_dev = spi_get_drvdata(spi);
>> +	struct dac8554_state *st = iio_priv(indio_dev);
>> +
>> +	iio_device_unregister(indio_dev);
>> +	regulator_disable(st->reg);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id dac8554_of_match[] = {
>> +	{ .compatible = "ti,dac8554" },
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(of, dac8554_of_match);
>> +
>> +static struct spi_driver dac8554_driver = {
>> +	.driver = {
>> +		.name = DAC8554_DRIVERNAME,
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = dac8554_of_match,
>> +	 },
>> +	.probe = dac8554_probe,
>> +	.remove = dac8554_remove,
>> +};
>> +module_spi_driver(dac8554_driver);
>> +
>> +MODULE_AUTHOR("Nikolaus Schulz <nikolaus.schulz-RM9K5IK7kjKj5M59NBduVrNAH6kLmebB@public.gmane.org>");
>> +MODULE_DESCRIPTION("Texas Instruments DAC8554 SPI driver");
>> +MODULE_LICENSE("GPL v2");
>>
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-iio" in
> the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2014-12-26 16:11 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-15 11:39 [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings Nikolaus Schulz
2014-12-15 11:39 ` Nikolaus Schulz
2014-12-15 11:39 ` [PATCH v3 2/2] iio: add driver for the TI DAC8554 Nikolaus Schulz
2014-12-18 21:30   ` Hartmut Knaack
2014-12-18 21:30     ` Hartmut Knaack
2014-12-26 11:51   ` Jonathan Cameron
2014-12-26 16:10     ` Hartmut Knaack
2014-12-26 16:10       ` Hartmut Knaack
2014-12-15 13:02 ` [PATCH v3 1/2] iio: document ti-dac8554 devicetree bindings Mark Rutland
2014-12-15 13:02   ` Mark Rutland
2014-12-15 13:15   ` Nikolaus Schulz
2014-12-15 13:15     ` Nikolaus Schulz

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.