2023-09-20 19:29:57

by David Lechner

[permalink] [raw]
Subject: [PATCH 0/4] iio: resolver: move ad2s1210 out of staging

This series moves the ad2s1210 resolver driver out of staging (after 13 years!).

We have made quite a few fixes and improvements to the driver (specifics are
listed in "iio: resolver: add new driver for AD2S1210"). The driver has been
tested on actual hardware using a EVAL-AD2S1210 evaluation board. (Note: not
all device tree features have been implemented in the driver since the eval
board doesn't support them out of the box. We plan to add them later if needed.)

One thing left over from the staging driver that probably needs more attention
still is the fault handling (both the fault threshold attributes and how
userspace gets notified of fault conditions). We considered adding these as
events, but the fault conditions are related to internal measurements in the
chip that aren't available as channels.

Since the chip is designed to read the fault register each time we read the
data registers for one of the two channels it seems like faults should be
associated with channels one way or another. Would it make sense to add extra
channels for the internal signals that only have fault events (mostly with
IIO_EV_TYPE_THRESH)? Or would it make sense to add a new "flags" channel type
where the "raw" value is bit flags? Or something else?

Here is the table of available faults for context. Sine/cosine inputs are
internal signals.

| Bit | Description
+-----+------------
| D7 | Sine/cosine inputs clipped
| D6 | Sine/cosine inputs below LOS threshold
| D5 | Sine/cosine inputs exceed DOS overrange threshold
| D4 | Sine/cosine inputs exceed DOS mismatch threshold
| D3 | Tracking error exceeds LOT threshold
| D2 | Velocity exceeds maximum tracking rate
| D1 | Phase error exceeds phase lock range
| D0 | Configuration parity error


David Lechner (4):
dt-bindings: iio: resolver: add devicetree bindings for ad2s1210
iio: sysfs: add IIO_DEVICE_ATTR_NAMED_RW macro
staging: iio: resolver: remove ad2s1210 driver
iio: resolver: add new driver for AD2S1210

.../testing/sysfs-bus-iio-resolver-ad2s1210 | 109 ++
.../bindings/iio/resolver/adi,ad2s1210.yaml | 150 +++
drivers/iio/resolver/Kconfig | 13 +
drivers/iio/resolver/Makefile | 1 +
drivers/iio/resolver/ad2s1210.c | 948 ++++++++++++++++++
drivers/staging/iio/Kconfig | 1 -
drivers/staging/iio/Makefile | 1 -
drivers/staging/iio/resolver/Kconfig | 18 -
drivers/staging/iio/resolver/Makefile | 6 -
drivers/staging/iio/resolver/ad2s1210.c | 716 -------------
include/linux/iio/sysfs.h | 4 +
11 files changed, 1225 insertions(+), 742 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
create mode 100644 Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
create mode 100644 drivers/iio/resolver/ad2s1210.c
delete mode 100644 drivers/staging/iio/resolver/Kconfig
delete mode 100644 drivers/staging/iio/resolver/Makefile
delete mode 100644 drivers/staging/iio/resolver/ad2s1210.c

--
2.34.1


2023-09-20 20:40:24

by David Lechner

[permalink] [raw]
Subject: [PATCH 2/4] iio: sysfs: add IIO_DEVICE_ATTR_NAMED_RW macro

This adds a new IIO_DEVICE_ATTR_NAMED_RW to handle the case where
multiple attributes share a common implementation so the attribute
name and the function names need to be different.

Signed-off-by: David Lechner <[email protected]>
---
include/linux/iio/sysfs.h | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/include/linux/iio/sysfs.h b/include/linux/iio/sysfs.h
index de5bb125815c..ab20c5294e52 100644
--- a/include/linux/iio/sysfs.h
+++ b/include/linux/iio/sysfs.h
@@ -87,6 +87,10 @@ struct iio_const_attr {
struct iio_dev_attr iio_dev_attr_##_vname \
= IIO_ATTR(_name, _mode, _show, _store, _addr)

+#define IIO_DEVICE_ATTR_NAMED_RW(_vname, _name, _addr) \
+ struct iio_dev_attr iio_dev_attr_##_vname \
+ = IIO_ATTR_RW(_name, _addr)
+
#define IIO_CONST_ATTR(_name, _string) \
struct iio_const_attr iio_const_attr_##_name \
= { .string = _string, \
--
2.34.1

2023-09-20 21:31:37

by David Lechner

[permalink] [raw]
Subject: [PATCH 1/4] dt-bindings: iio: resolver: add devicetree bindings for ad2s1210

This adds new DeviceTree bindings for the Analog Devices, Inc. AD2S1210
resolver-to-digital converter.

Signed-off-by: Apelete Seketeli <[email protected]>
Signed-off-by: David Lechner <[email protected]>
---
.../bindings/iio/resolver/adi,ad2s1210.yaml | 150 ++++++++++++++++++
1 file changed, 150 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml

diff --git a/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml b/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
new file mode 100644
index 000000000000..cf6838710e52
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
@@ -0,0 +1,150 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/resolver/adi,ad2s1210.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD2S1210 Resolver-to-Digital Converter
+
+maintainers:
+ - Michael Hennerich <[email protected]>
+
+description: |
+ The AD2S1210 is a complete 10-bit to 16-bit resolution tracking
+ resolver-to-digital converter, integrating an on-board programmable
+ sinusoidal oscillator that provides sine wave excitation for
+ resolvers.
+
+ The AD2S1210 allows the user to read the angular position or the
+ angular velocity data directly from the parallel outputs or through
+ the serial interface.
+
+ A1 A0 Result
+ 0 0 Normal mode - position output
+ 0 1 Normal mode - velocity output
+ 1 0 Reserved
+ 1 1 Configuration mode
+
+ In normal mode, the resolution of the digital output is selected using
+ the RES0 and RES1 input pins. In configuration mode, the resolution is
+ selected by setting the RES0 and RES1 bits in the control register.
+
+ RES1 RES0 Resolution (Bits)
+ 0 0 10
+ 0 1 12
+ 1 0 14
+ 1 1 16
+
+ Note on SPI connections: The CS line on the AD2S1210 should hard-wired to
+ logic low and the WR/FSYNC line on the AD2S1210 should be connected to the
+ SPI CSn output of the SPI controller.
+
+ Datasheet:
+ https://www.analog.com/media/en/technical-documentation/data-sheets/ad2s1210.pdf
+
+properties:
+ compatible:
+ const: adi,ad2s1210
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+ spi-cpha: true
+
+ clocks:
+ maxItems: 1
+ description: External oscillator clock (CLKIN).
+
+ reset-gpios:
+ description:
+ GPIO connected to the /RESET pin. As the line needs to be low for the
+ reset to be active, it should be configured as GPIO_ACTIVE_LOW.
+ maxItems: 1
+
+ sample-gpios:
+ description: |
+ GPIO connected to the /SAMPLE pin. As the line needs to be low to trigger
+ a sample, it should be configured as GPIO_ACTIVE_LOW.
+ maxItems: 1
+
+ mode-gpios:
+ description: |
+ GPIO lines connected to the A0 and A1 pins. These pins select the data
+ transfer mode.
+ minItems: 2
+ maxItems: 2
+
+ resolution-gpios:
+ description: |
+ GPIO lines connected to the RES0 and RES1 pins. These pins select the
+ resolution of the digital output. If omitted, it is assumed that the
+ RES0 and RES1 pins are hard-wired to match the assigned-resolution-bits
+ property.
+ minItems: 2
+ maxItems: 2
+
+ fault-gpios:
+ description: |
+ GPIO lines connected to the LOT and DOS pins. These pins combined indicate
+ the type of fault present, if any. As these pins a pulled low to indicate
+ a fault condition, they should be configured as GPIO_ACTIVE_LOW.
+ minItems: 2
+ maxItems: 2
+
+ adi,fixed-mode:
+ description: |
+ This is used to indicate the selected mode if A0 and A1 are hard-wired
+ instead of connected to GPIOS (i.e. mode-gpios is omitted).
+ $ref: /schemas/types.yaml#/definitions/string
+ enum: ["config", "velocity", "position"]
+
+ assigned-resolution-bits:
+ description: |
+ Resolution of the digital output required by the application. This
+ determines the precision of the angle and/or the maximum speed that can
+ be measured. If resolution-gpios is omitted, it is assumed that RES0 and
+ RES1 are hard-wired to match this value.
+ enum: [10, 12, 14, 16]
+
+required:
+ - compatible
+ - reg
+ - spi-cpha
+ - clocks
+ - sample-gpios
+ - assigned-resolution-bits
+
+oneOf:
+ - required:
+ - mode-gpios
+ - required:
+ - adi,fixed-mode
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ resolver@0 {
+ compatible = "adi,ad2s1210";
+ reg = <0>;
+ spi-max-frequency = <20000000>;
+ spi-cpha;
+ clocks = <&ext_osc>;
+ sample-gpios = <&gpio0 90 GPIO_ACTIVE_LOW>;
+ mode-gpios = <&gpio0 86 0>, <&gpio0 87 0>;
+ resolution-gpios = <&gpio0 88 0>, <&gpio0 89 0>;
+ assigned-resolution-bits = <16>;
+ };
+ };
--
2.34.1

2023-09-20 22:39:42

by David Lechner

[permalink] [raw]
Subject: [PATCH 3/4] staging: iio: resolver: remove ad2s1210 driver

This removes the AD2S1210 resolver driver from staging. It will be
improved and resubmitted out of staging.

Signed-off-by: David Lechner <[email protected]>
---
drivers/staging/iio/Kconfig | 1 -
drivers/staging/iio/Makefile | 1 -
drivers/staging/iio/resolver/Kconfig | 18 -
drivers/staging/iio/resolver/Makefile | 6 -
drivers/staging/iio/resolver/ad2s1210.c | 716 ------------------------
5 files changed, 742 deletions(-)
delete mode 100644 drivers/staging/iio/resolver/Kconfig
delete mode 100644 drivers/staging/iio/resolver/Makefile
delete mode 100644 drivers/staging/iio/resolver/ad2s1210.c

diff --git a/drivers/staging/iio/Kconfig b/drivers/staging/iio/Kconfig
index d3968fe2ebb8..a60631c1f449 100644
--- a/drivers/staging/iio/Kconfig
+++ b/drivers/staging/iio/Kconfig
@@ -10,6 +10,5 @@ source "drivers/staging/iio/adc/Kconfig"
source "drivers/staging/iio/addac/Kconfig"
source "drivers/staging/iio/frequency/Kconfig"
source "drivers/staging/iio/impedance-analyzer/Kconfig"
-source "drivers/staging/iio/resolver/Kconfig"

endmenu
diff --git a/drivers/staging/iio/Makefile b/drivers/staging/iio/Makefile
index c50f1019f829..628583535393 100644
--- a/drivers/staging/iio/Makefile
+++ b/drivers/staging/iio/Makefile
@@ -8,4 +8,3 @@ obj-y += adc/
obj-y += addac/
obj-y += frequency/
obj-y += impedance-analyzer/
-obj-y += resolver/
diff --git a/drivers/staging/iio/resolver/Kconfig b/drivers/staging/iio/resolver/Kconfig
deleted file mode 100644
index 6d1e2622e0b0..000000000000
--- a/drivers/staging/iio/resolver/Kconfig
+++ /dev/null
@@ -1,18 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-#
-# Resolver/Synchro drivers
-#
-menu "Resolver to digital converters"
-
-config AD2S1210
- tristate "Analog Devices ad2s1210 driver"
- depends on SPI
- depends on GPIOLIB || COMPILE_TEST
- help
- Say yes here to build support for Analog Devices spi resolver
- to digital converters, ad2s1210, provides direct access via sysfs.
-
- To compile this driver as a module, choose M here: the
- module will be called ad2s1210.
-
-endmenu
diff --git a/drivers/staging/iio/resolver/Makefile b/drivers/staging/iio/resolver/Makefile
deleted file mode 100644
index 398631f7e79b..000000000000
--- a/drivers/staging/iio/resolver/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0
-#
-# Makefile for Resolver/Synchro drivers
-#
-
-obj-$(CONFIG_AD2S1210) += ad2s1210.o
diff --git a/drivers/staging/iio/resolver/ad2s1210.c b/drivers/staging/iio/resolver/ad2s1210.c
deleted file mode 100644
index 06de5823eb8e..000000000000
--- a/drivers/staging/iio/resolver/ad2s1210.c
+++ /dev/null
@@ -1,716 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210
- *
- * Copyright (c) 2010-2010 Analog Devices Inc.
- */
-#include <linux/types.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
-#include <linux/slab.h>
-#include <linux/sysfs.h>
-#include <linux/delay.h>
-#include <linux/gpio/consumer.h>
-#include <linux/module.h>
-
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
-
-#define DRV_NAME "ad2s1210"
-
-#define AD2S1210_DEF_CONTROL 0x7E
-
-#define AD2S1210_MSB_IS_HIGH 0x80
-#define AD2S1210_MSB_IS_LOW 0x7F
-#define AD2S1210_PHASE_LOCK_RANGE_44 0x20
-#define AD2S1210_ENABLE_HYSTERESIS 0x10
-#define AD2S1210_SET_ENRES1 0x08
-#define AD2S1210_SET_ENRES0 0x04
-#define AD2S1210_SET_RES1 0x02
-#define AD2S1210_SET_RES0 0x01
-
-#define AD2S1210_SET_RESOLUTION (AD2S1210_SET_RES1 | AD2S1210_SET_RES0)
-
-#define AD2S1210_REG_POSITION 0x80
-#define AD2S1210_REG_VELOCITY 0x82
-#define AD2S1210_REG_LOS_THRD 0x88
-#define AD2S1210_REG_DOS_OVR_THRD 0x89
-#define AD2S1210_REG_DOS_MIS_THRD 0x8A
-#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B
-#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C
-#define AD2S1210_REG_LOT_HIGH_THRD 0x8D
-#define AD2S1210_REG_LOT_LOW_THRD 0x8E
-#define AD2S1210_REG_EXCIT_FREQ 0x91
-#define AD2S1210_REG_CONTROL 0x92
-#define AD2S1210_REG_SOFT_RESET 0xF0
-#define AD2S1210_REG_FAULT 0xFF
-
-#define AD2S1210_MIN_CLKIN 6144000
-#define AD2S1210_MAX_CLKIN 10240000
-#define AD2S1210_MIN_EXCIT 2000
-#define AD2S1210_MAX_EXCIT 20000
-#define AD2S1210_MIN_FCW 0x4
-#define AD2S1210_MAX_FCW 0x50
-
-#define AD2S1210_DEF_EXCIT 10000
-
-enum ad2s1210_mode {
- MOD_POS = 0,
- MOD_VEL,
- MOD_CONFIG,
- MOD_RESERVED,
-};
-
-enum ad2s1210_gpios {
- AD2S1210_SAMPLE,
- AD2S1210_A0,
- AD2S1210_A1,
- AD2S1210_RES0,
- AD2S1210_RES1,
-};
-
-struct ad2s1210_gpio {
- const char *name;
- unsigned long flags;
-};
-
-static const struct ad2s1210_gpio gpios[] = {
- [AD2S1210_SAMPLE] = { .name = "adi,sample", .flags = GPIOD_OUT_LOW },
- [AD2S1210_A0] = { .name = "adi,a0", .flags = GPIOD_OUT_LOW },
- [AD2S1210_A1] = { .name = "adi,a1", .flags = GPIOD_OUT_LOW },
- [AD2S1210_RES0] = { .name = "adi,res0", .flags = GPIOD_OUT_LOW },
- [AD2S1210_RES1] = { .name = "adi,res1", .flags = GPIOD_OUT_LOW },
-};
-
-static const unsigned int ad2s1210_resolution_value[] = { 10, 12, 14, 16 };
-
-struct ad2s1210_state {
- struct mutex lock;
- struct spi_device *sdev;
- struct gpio_desc *gpios[5];
- unsigned int fclkin;
- unsigned int fexcit;
- bool hysteresis;
- u8 resolution;
- enum ad2s1210_mode mode;
- u8 rx[2] __aligned(IIO_DMA_MINALIGN);
- u8 tx[2];
-};
-
-static const int ad2s1210_mode_vals[4][2] = {
- [MOD_POS] = { 0, 0 },
- [MOD_VEL] = { 0, 1 },
- [MOD_CONFIG] = { 1, 1 },
-};
-
-static inline void ad2s1210_set_mode(enum ad2s1210_mode mode,
- struct ad2s1210_state *st)
-{
- gpiod_set_value(st->gpios[AD2S1210_A0], ad2s1210_mode_vals[mode][0]);
- gpiod_set_value(st->gpios[AD2S1210_A1], ad2s1210_mode_vals[mode][1]);
- st->mode = mode;
-}
-
-/* write 1 bytes (address or data) to the chip */
-static int ad2s1210_config_write(struct ad2s1210_state *st, u8 data)
-{
- int ret;
-
- ad2s1210_set_mode(MOD_CONFIG, st);
- st->tx[0] = data;
- ret = spi_write(st->sdev, st->tx, 1);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
-/* read value from one of the registers */
-static int ad2s1210_config_read(struct ad2s1210_state *st,
- unsigned char address)
-{
- struct spi_transfer xfers[] = {
- {
- .len = 1,
- .rx_buf = &st->rx[0],
- .tx_buf = &st->tx[0],
- .cs_change = 1,
- }, {
- .len = 1,
- .rx_buf = &st->rx[1],
- .tx_buf = &st->tx[1],
- },
- };
- int ret = 0;
-
- ad2s1210_set_mode(MOD_CONFIG, st);
- st->tx[0] = address | AD2S1210_MSB_IS_HIGH;
- st->tx[1] = AD2S1210_REG_FAULT;
- ret = spi_sync_transfer(st->sdev, xfers, 2);
- if (ret < 0)
- return ret;
-
- return st->rx[1];
-}
-
-static inline
-int ad2s1210_update_frequency_control_word(struct ad2s1210_state *st)
-{
- int ret;
- unsigned char fcw;
-
- fcw = (unsigned char)(st->fexcit * (1 << 15) / st->fclkin);
- if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW) {
- dev_err(&st->sdev->dev, "ad2s1210: FCW out of range\n");
- return -ERANGE;
- }
-
- ret = ad2s1210_config_write(st, AD2S1210_REG_EXCIT_FREQ);
- if (ret < 0)
- return ret;
-
- return ad2s1210_config_write(st, fcw);
-}
-
-static const int ad2s1210_res_pins[4][2] = {
- { 0, 0 }, {0, 1}, {1, 0}, {1, 1}
-};
-
-static inline void ad2s1210_set_resolution_pin(struct ad2s1210_state *st)
-{
- gpiod_set_value(st->gpios[AD2S1210_RES0],
- ad2s1210_res_pins[(st->resolution - 10) / 2][0]);
- gpiod_set_value(st->gpios[AD2S1210_RES1],
- ad2s1210_res_pins[(st->resolution - 10) / 2][1]);
-}
-
-static inline int ad2s1210_soft_reset(struct ad2s1210_state *st)
-{
- int ret;
-
- ret = ad2s1210_config_write(st, AD2S1210_REG_SOFT_RESET);
- if (ret < 0)
- return ret;
-
- return ad2s1210_config_write(st, 0x0);
-}
-
-static ssize_t ad2s1210_show_fclkin(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
-
- return sprintf(buf, "%u\n", st->fclkin);
-}
-
-static ssize_t ad2s1210_store_fclkin(struct device *dev,
- struct device_attribute *attr,
- const char *buf,
- size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- unsigned int fclkin;
- int ret;
-
- ret = kstrtouint(buf, 10, &fclkin);
- if (ret)
- return ret;
- if (fclkin < AD2S1210_MIN_CLKIN || fclkin > AD2S1210_MAX_CLKIN) {
- dev_err(dev, "ad2s1210: fclkin out of range\n");
- return -EINVAL;
- }
-
- mutex_lock(&st->lock);
- st->fclkin = fclkin;
-
- ret = ad2s1210_update_frequency_control_word(st);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_soft_reset(st);
-error_ret:
- mutex_unlock(&st->lock);
-
- return ret < 0 ? ret : len;
-}
-
-static ssize_t ad2s1210_show_fexcit(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
-
- return sprintf(buf, "%u\n", st->fexcit);
-}
-
-static ssize_t ad2s1210_store_fexcit(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- unsigned int fexcit;
- int ret;
-
- ret = kstrtouint(buf, 10, &fexcit);
- if (ret < 0)
- return ret;
- if (fexcit < AD2S1210_MIN_EXCIT || fexcit > AD2S1210_MAX_EXCIT) {
- dev_err(dev,
- "ad2s1210: excitation frequency out of range\n");
- return -EINVAL;
- }
- mutex_lock(&st->lock);
- st->fexcit = fexcit;
- ret = ad2s1210_update_frequency_control_word(st);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_soft_reset(st);
-error_ret:
- mutex_unlock(&st->lock);
-
- return ret < 0 ? ret : len;
-}
-
-static ssize_t ad2s1210_show_control(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- int ret;
-
- mutex_lock(&st->lock);
- ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL);
- mutex_unlock(&st->lock);
- return ret < 0 ? ret : sprintf(buf, "0x%x\n", ret);
-}
-
-static ssize_t ad2s1210_store_control(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- unsigned char udata;
- unsigned char data;
- int ret;
-
- ret = kstrtou8(buf, 16, &udata);
- if (ret)
- return -EINVAL;
-
- mutex_lock(&st->lock);
- ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- data = udata & AD2S1210_MSB_IS_LOW;
- ret = ad2s1210_config_write(st, data);
- if (ret < 0)
- goto error_ret;
-
- ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- if (ret & AD2S1210_MSB_IS_HIGH) {
- ret = -EIO;
- dev_err(dev,
- "ad2s1210: write control register fail\n");
- goto error_ret;
- }
- st->resolution =
- ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION];
- ad2s1210_set_resolution_pin(st);
- ret = len;
- st->hysteresis = !!(data & AD2S1210_ENABLE_HYSTERESIS);
-
-error_ret:
- mutex_unlock(&st->lock);
- return ret;
-}
-
-static ssize_t ad2s1210_show_resolution(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
-
- return sprintf(buf, "%d\n", st->resolution);
-}
-
-static ssize_t ad2s1210_store_resolution(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- unsigned char data;
- unsigned char udata;
- int ret;
-
- ret = kstrtou8(buf, 10, &udata);
- if (ret || udata < 10 || udata > 16) {
- dev_err(dev, "ad2s1210: resolution out of range\n");
- return -EINVAL;
- }
- mutex_lock(&st->lock);
- ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- data = ret;
- data &= ~AD2S1210_SET_RESOLUTION;
- data |= (udata - 10) >> 1;
- ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- data = ret;
- if (data & AD2S1210_MSB_IS_HIGH) {
- ret = -EIO;
- dev_err(dev, "ad2s1210: setting resolution fail\n");
- goto error_ret;
- }
- st->resolution =
- ad2s1210_resolution_value[data & AD2S1210_SET_RESOLUTION];
- ad2s1210_set_resolution_pin(st);
- ret = len;
-error_ret:
- mutex_unlock(&st->lock);
- return ret;
-}
-
-/* read the fault register since last sample */
-static ssize_t ad2s1210_show_fault(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- int ret;
-
- mutex_lock(&st->lock);
- ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT);
- mutex_unlock(&st->lock);
-
- return ret ? ret : sprintf(buf, "0x%x\n", ret);
-}
-
-static ssize_t ad2s1210_clear_fault(struct device *dev,
- struct device_attribute *attr,
- const char *buf,
- size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- int ret;
-
- mutex_lock(&st->lock);
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 0);
- /* delay (2 * tck + 20) nano seconds */
- udelay(1);
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 1);
- ret = ad2s1210_config_read(st, AD2S1210_REG_FAULT);
- if (ret < 0)
- goto error_ret;
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 0);
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 1);
-error_ret:
- mutex_unlock(&st->lock);
-
- return ret < 0 ? ret : len;
-}
-
-static ssize_t ad2s1210_show_reg(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
- int ret;
-
- mutex_lock(&st->lock);
- ret = ad2s1210_config_read(st, iattr->address);
- mutex_unlock(&st->lock);
-
- return ret < 0 ? ret : sprintf(buf, "%d\n", ret);
-}
-
-static ssize_t ad2s1210_store_reg(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
- unsigned char data;
- int ret;
- struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
-
- ret = kstrtou8(buf, 10, &data);
- if (ret)
- return -EINVAL;
- mutex_lock(&st->lock);
- ret = ad2s1210_config_write(st, iattr->address);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_config_write(st, data & AD2S1210_MSB_IS_LOW);
-error_ret:
- mutex_unlock(&st->lock);
- return ret < 0 ? ret : len;
-}
-
-static int ad2s1210_read_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int *val,
- int *val2,
- long m)
-{
- struct ad2s1210_state *st = iio_priv(indio_dev);
- u16 negative;
- int ret = 0;
- u16 pos;
- s16 vel;
-
- mutex_lock(&st->lock);
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 0);
- /* delay (6 * tck + 20) nano seconds */
- udelay(1);
-
- switch (chan->type) {
- case IIO_ANGL:
- ad2s1210_set_mode(MOD_POS, st);
- break;
- case IIO_ANGL_VEL:
- ad2s1210_set_mode(MOD_VEL, st);
- break;
- default:
- ret = -EINVAL;
- break;
- }
- if (ret < 0)
- goto error_ret;
- ret = spi_read(st->sdev, st->rx, 2);
- if (ret < 0)
- goto error_ret;
-
- switch (chan->type) {
- case IIO_ANGL:
- pos = be16_to_cpup((__be16 *)st->rx);
- if (st->hysteresis)
- pos >>= 16 - st->resolution;
- *val = pos;
- ret = IIO_VAL_INT;
- break;
- case IIO_ANGL_VEL:
- vel = be16_to_cpup((__be16 *)st->rx);
- vel >>= 16 - st->resolution;
- if (vel & 0x8000) {
- negative = (0xffff >> st->resolution) << st->resolution;
- vel |= negative;
- }
- *val = vel;
- ret = IIO_VAL_INT;
- break;
- default:
- mutex_unlock(&st->lock);
- return -EINVAL;
- }
-
-error_ret:
- gpiod_set_value(st->gpios[AD2S1210_SAMPLE], 1);
- /* delay (2 * tck + 20) nano seconds */
- udelay(1);
- mutex_unlock(&st->lock);
- return ret;
-}
-
-static IIO_DEVICE_ATTR(fclkin, 0644,
- ad2s1210_show_fclkin, ad2s1210_store_fclkin, 0);
-static IIO_DEVICE_ATTR(fexcit, 0644,
- ad2s1210_show_fexcit, ad2s1210_store_fexcit, 0);
-static IIO_DEVICE_ATTR(control, 0644,
- ad2s1210_show_control, ad2s1210_store_control, 0);
-static IIO_DEVICE_ATTR(bits, 0644,
- ad2s1210_show_resolution, ad2s1210_store_resolution, 0);
-static IIO_DEVICE_ATTR(fault, 0644,
- ad2s1210_show_fault, ad2s1210_clear_fault, 0);
-
-static IIO_DEVICE_ATTR(los_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_LOS_THRD);
-static IIO_DEVICE_ATTR(dos_ovr_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_DOS_OVR_THRD);
-static IIO_DEVICE_ATTR(dos_mis_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_DOS_MIS_THRD);
-static IIO_DEVICE_ATTR(dos_rst_max_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_DOS_RST_MAX_THRD);
-static IIO_DEVICE_ATTR(dos_rst_min_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_DOS_RST_MIN_THRD);
-static IIO_DEVICE_ATTR(lot_high_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_LOT_HIGH_THRD);
-static IIO_DEVICE_ATTR(lot_low_thrd, 0644,
- ad2s1210_show_reg, ad2s1210_store_reg,
- AD2S1210_REG_LOT_LOW_THRD);
-
-static const struct iio_chan_spec ad2s1210_channels[] = {
- {
- .type = IIO_ANGL,
- .indexed = 1,
- .channel = 0,
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
- }, {
- .type = IIO_ANGL_VEL,
- .indexed = 1,
- .channel = 0,
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
- }
-};
-
-static struct attribute *ad2s1210_attributes[] = {
- &iio_dev_attr_fclkin.dev_attr.attr,
- &iio_dev_attr_fexcit.dev_attr.attr,
- &iio_dev_attr_control.dev_attr.attr,
- &iio_dev_attr_bits.dev_attr.attr,
- &iio_dev_attr_fault.dev_attr.attr,
- &iio_dev_attr_los_thrd.dev_attr.attr,
- &iio_dev_attr_dos_ovr_thrd.dev_attr.attr,
- &iio_dev_attr_dos_mis_thrd.dev_attr.attr,
- &iio_dev_attr_dos_rst_max_thrd.dev_attr.attr,
- &iio_dev_attr_dos_rst_min_thrd.dev_attr.attr,
- &iio_dev_attr_lot_high_thrd.dev_attr.attr,
- &iio_dev_attr_lot_low_thrd.dev_attr.attr,
- NULL,
-};
-
-static const struct attribute_group ad2s1210_attribute_group = {
- .attrs = ad2s1210_attributes,
-};
-
-static int ad2s1210_initial(struct ad2s1210_state *st)
-{
- unsigned char data;
- int ret;
-
- mutex_lock(&st->lock);
- ad2s1210_set_resolution_pin(st);
-
- ret = ad2s1210_config_write(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
- data = AD2S1210_DEF_CONTROL & ~(AD2S1210_SET_RESOLUTION);
- data |= (st->resolution - 10) >> 1;
- ret = ad2s1210_config_write(st, data);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_config_read(st, AD2S1210_REG_CONTROL);
- if (ret < 0)
- goto error_ret;
-
- if (ret & AD2S1210_MSB_IS_HIGH) {
- ret = -EIO;
- goto error_ret;
- }
-
- ret = ad2s1210_update_frequency_control_word(st);
- if (ret < 0)
- goto error_ret;
- ret = ad2s1210_soft_reset(st);
-error_ret:
- mutex_unlock(&st->lock);
- return ret;
-}
-
-static const struct iio_info ad2s1210_info = {
- .read_raw = ad2s1210_read_raw,
- .attrs = &ad2s1210_attribute_group,
-};
-
-static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
-{
- struct spi_device *spi = st->sdev;
- int i, ret;
-
- for (i = 0; i < ARRAY_SIZE(gpios); i++) {
- st->gpios[i] = devm_gpiod_get(&spi->dev, gpios[i].name,
- gpios[i].flags);
- if (IS_ERR(st->gpios[i])) {
- ret = PTR_ERR(st->gpios[i]);
- dev_err(&spi->dev,
- "ad2s1210: failed to request %s GPIO: %d\n",
- gpios[i].name, ret);
- return ret;
- }
- }
-
- return 0;
-}
-
-static int ad2s1210_probe(struct spi_device *spi)
-{
- struct iio_dev *indio_dev;
- struct ad2s1210_state *st;
- int ret;
-
- indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
- if (!indio_dev)
- return -ENOMEM;
- st = iio_priv(indio_dev);
- ret = ad2s1210_setup_gpios(st);
- if (ret < 0)
- return ret;
-
- spi_set_drvdata(spi, indio_dev);
-
- mutex_init(&st->lock);
- st->sdev = spi;
- st->hysteresis = true;
- st->mode = MOD_CONFIG;
- st->resolution = 12;
- st->fexcit = AD2S1210_DEF_EXCIT;
-
- indio_dev->info = &ad2s1210_info;
- indio_dev->modes = INDIO_DIRECT_MODE;
- indio_dev->channels = ad2s1210_channels;
- indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels);
- indio_dev->name = spi_get_device_id(spi)->name;
-
- ret = devm_iio_device_register(&spi->dev, indio_dev);
- if (ret)
- return ret;
-
- st->fclkin = spi->max_speed_hz;
- spi->mode = SPI_MODE_3;
- spi_setup(spi);
- ad2s1210_initial(st);
-
- return 0;
-}
-
-static const struct of_device_id ad2s1210_of_match[] = {
- { .compatible = "adi,ad2s1210", },
- { }
-};
-MODULE_DEVICE_TABLE(of, ad2s1210_of_match);
-
-static const struct spi_device_id ad2s1210_id[] = {
- { "ad2s1210" },
- {}
-};
-MODULE_DEVICE_TABLE(spi, ad2s1210_id);
-
-static struct spi_driver ad2s1210_driver = {
- .driver = {
- .name = DRV_NAME,
- .of_match_table = of_match_ptr(ad2s1210_of_match),
- },
- .probe = ad2s1210_probe,
- .id_table = ad2s1210_id,
-};
-module_spi_driver(ad2s1210_driver);
-
-MODULE_AUTHOR("Graff Yang <[email protected]>");
-MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver");
-MODULE_LICENSE("GPL v2");
--
2.34.1

2023-09-20 22:57:42

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 1/4] dt-bindings: iio: resolver: add devicetree bindings for ad2s1210


On Wed, 20 Sep 2023 12:02:50 -0500, David Lechner wrote:
> This adds new DeviceTree bindings for the Analog Devices, Inc. AD2S1210
> resolver-to-digital converter.
>
> Signed-off-by: Apelete Seketeli <[email protected]>
> Signed-off-by: David Lechner <[email protected]>
> ---
> .../bindings/iio/resolver/adi,ad2s1210.yaml | 150 ++++++++++++++++++
> 1 file changed, 150 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:
./Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml:102:12: [error] string value is redundantly quoted with any quotes (quoted-strings)
./Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml:102:22: [error] string value is redundantly quoted with any quotes (quoted-strings)
./Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml:102:34: [error] string value is redundantly quoted with any quotes (quoted-strings)

dtschema/dtc warnings/errors:

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/[email protected]

The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.

2023-09-20 23:33:55

by David Lechner

[permalink] [raw]
Subject: [PATCH 4/4] iio: resolver: add new driver for AD2S1210

This adds a new driver for Analog Devices, Inc. AD2S1210 resolver to
digital converter. The driver is based on the staging driver with the
following improvements:

Fixes:
- Fix use before initialization bug in probe.
- Fix not checking error returns in probe.
- Remove spi_setup() and spi_set_drvdata() from probe.
- Fix ordering of devm_iio_device_register()
- Remove incorrect hysteresis logic

Changes:
- Use BIT/GENMASK macros.
- Use devicetree to get CLKIN frequency (was sysfs attribute).
- No longer bit-shift the raw value for IIO_CHAN_INFO_RAW.
- Use regmap for register access.
- Remove config sysfs attribute.
- Use gpio array for mode and resolution gpios.
- Invert sample gpio logic and use GPIO_ACTIVE_LOW in devicetree.
- Change hysteresis to use IIO_CHAN_INFO_HYSTERESIS instead of custom
device attribute.
- Rename fexcit attribute to excitation_frequency.
- Use devicetree to specify resolution instead of sysfs attribute.

Additions:
- Implement IIO_CHAN_INFO_SCALE.
- Implement debugfs register access.
- Add phase_lock_range attribute.
- Add triggered buffer support.

Signed-off-by: David Lechner <[email protected]>
---
.../testing/sysfs-bus-iio-resolver-ad2s1210 | 109 ++
drivers/iio/resolver/Kconfig | 13 +
drivers/iio/resolver/Makefile | 1 +
drivers/iio/resolver/ad2s1210.c | 948 ++++++++++++++++++
4 files changed, 1071 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
create mode 100644 drivers/iio/resolver/ad2s1210.c

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210 b/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
new file mode 100644
index 000000000000..32890c85168e
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
@@ -0,0 +1,109 @@
+What: /sys/bus/iio/devices/iio:deviceX/dos_mis_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Degradation of Signal Mismatch
+ Threshold value. Writing sets the value. Valid values are 0 (0V)
+ to 127 (4.826V). To convert the value to volts, multiply by
+ 0.038.
+
+What: /sys/bus/iio/devices/iio:deviceX/dos_ovr_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Degradation of Signal Overrange
+ Threshold value. Writing sets the value. Valid values are 0 (0V)
+ to 127 (4.826V). To convert the value to volts, multiply by
+ 0.038.
+
+What: /sys/bus/iio/devices/iio:deviceX/dos_rst_max_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Degradation of Signal Reset Maximum
+ Threshold value. Writing sets the value. Valid values are 0 (0V)
+ to 127 (4.826V). To convert the value to volts, multiply by
+ 0.038.
+
+What: /sys/bus/iio/devices/iio:deviceX/dos_rst_min_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Degradation of Signal Reset Minimum
+ Threshold value. Writing sets the value. Valid values are 0 (0V)
+ to 127 (4.826V). To convert the value to volts, multiply by
+ 0.038.
+
+What: /sys/bus/iio/devices/iio:deviceX/fault
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns a hex value containing the fault bit flags.
+
+ Bit Description
+ --- -----------
+ D7 Sine/cosine inputs clipped
+ D6 Sine/cosine inputs below LOS threshold
+ D5 Sine/cosine inputs exceed DOS overrange threshold
+ D4 Sine/cosine inputs exceed DOS mismatch threshold
+ D3 Tracking error exceeds LOT threshold
+ D2 Velocity exceeds maximum tracking rate
+ D1 Phase error exceeds phase lock range
+ D0 Configuration parity error
+
+ Writing any value will clear any fault conditions.
+
+What: /sys/bus/iio/devices/iio:deviceX/excitation_frequency
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Excitation Frequency in Hz. Writing
+ sets the Excitation Frequency and performs a software reset on
+ the device to apply the change. Valid values are 2000 (2kHz) to
+ 20000 (20kHz).
+
+What: /sys/bus/iio/devices/iio:deviceX/los_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Loss of Signal Reset Threshold
+ value. Writing sets the value. Valid values are 0 (0V) to
+ 127 (4.826V). To convert the value to volts, multiply by 0.038.
+
+What: /sys/bus/iio/devices/iio:deviceX/lot_high_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Loss of Position Tracking Detection
+ High Threshold value. Writing sets the value. Valid values are
+ 0 (0 deg) to 127 (9/18/45 deg). The interpretation of the value
+ depends on the selected resolution. To convert the value to
+ degrees, multiply by 0.35 for 10-bit resolution, multiply by
+ 0.14 for 12-bit resolution or multiply by 0.09 for 14 and 16-bit
+ resolution.
+
+What: /sys/bus/iio/devices/iio:deviceX/lot_low_thrd
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Loss of Position Tracking Detection
+ Low Threshold value. Writing sets the value. Valid values are
+ 0 (0 deg) to 127 (9/18/45 deg). The interpretation of the value
+ depends on the selected resolution. To convert the value to
+ degrees, multiply by 0.35 for 10-bit resolution, multiply by
+ 0.14 for 12-bit resolution or multiply by 0.09 for 14 and 16-bit
+ resolution.
+
+What: /sys/bus/iio/devices/iio:deviceX/phase_lock_range
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the current Phase lock range in degrees. Writing
+ sets the value in the configuration register.
+
+What: /sys/bus/iio/devices/iio:deviceX/phase_lock_range_available
+KernelVersion: 6.7
+Contact: [email protected]
+Description:
+ Reading returns the possible values for the phase_lock_range
+ attribute, namely 44 and 360.
diff --git a/drivers/iio/resolver/Kconfig b/drivers/iio/resolver/Kconfig
index 47dbfead9b31..424529d36080 100644
--- a/drivers/iio/resolver/Kconfig
+++ b/drivers/iio/resolver/Kconfig
@@ -25,4 +25,17 @@ config AD2S1200

To compile this driver as a module, choose M here: the
module will be called ad2s1200.
+
+config AD2S1210
+ tristate "Analog Devices ad2s1210 driver"
+ depends on SPI
+ depends on COMMON_CLK
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say yes here to build support for Analog Devices spi resolver
+ to digital converters, ad2s1210, provides direct access via sysfs.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ad2s1210.
+
endmenu
diff --git a/drivers/iio/resolver/Makefile b/drivers/iio/resolver/Makefile
index fa558138ce45..7f6c876c35ae 100644
--- a/drivers/iio/resolver/Makefile
+++ b/drivers/iio/resolver/Makefile
@@ -5,3 +5,4 @@

obj-$(CONFIG_AD2S90) += ad2s90.o
obj-$(CONFIG_AD2S1200) += ad2s1200.o
+obj-$(CONFIG_AD2S1210) += ad2s1210.o
diff --git a/drivers/iio/resolver/ad2s1210.c b/drivers/iio/resolver/ad2s1210.c
new file mode 100644
index 000000000000..97833fbcbf7a
--- /dev/null
+++ b/drivers/iio/resolver/ad2s1210.c
@@ -0,0 +1,948 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210
+ *
+ * Copyright (c) 2010-2010 Analog Devices Inc.
+ * Copyright (C) 2023 BayLibre, SAS
+ */
+
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#define DRV_NAME "ad2s1210"
+
+/* default value of control register on powerup */
+#define AD2S1210_DEF_CONTROL 0x7E
+
+/* control register flags */
+#define AD2S1210_ADDRESS_DATA BIT(7)
+#define AD2S1210_PHASE_LOCK_RANGE_44 BIT(5)
+#define AD2S1210_ENABLE_HYSTERESIS BIT(4)
+#define AD2S1210_SET_ENRES GENMASK(3, 2)
+#define AD2S1210_SET_RES GENMASK(1, 0)
+
+#define AD2S1210_REG_POSITION_MSB 0x80
+#define AD2S1210_REG_POSITION_LSB 0x81
+#define AD2S1210_REG_VELOCITY_MSB 0x82
+#define AD2S1210_REG_VELOCITY_LSB 0x83
+#define AD2S1210_REG_LOS_THRD 0x88
+#define AD2S1210_REG_DOS_OVR_THRD 0x89
+#define AD2S1210_REG_DOS_MIS_THRD 0x8A
+#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B
+#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C
+#define AD2S1210_REG_LOT_HIGH_THRD 0x8D
+#define AD2S1210_REG_LOT_LOW_THRD 0x8E
+#define AD2S1210_REG_EXCIT_FREQ 0x91
+#define AD2S1210_REG_CONTROL 0x92
+#define AD2S1210_REG_SOFT_RESET 0xF0
+#define AD2S1210_REG_FAULT 0xFF
+
+#define AD2S1210_MIN_CLKIN 6144000
+#define AD2S1210_MAX_CLKIN 10240000
+#define AD2S1210_MIN_EXCIT 2000
+#define AD2S1210_DEF_EXCIT 10000
+#define AD2S1210_MAX_EXCIT 20000
+#define AD2S1210_MIN_FCW 0x4
+#define AD2S1210_MAX_FCW 0x50
+
+enum ad2s1210_mode {
+ AD2S1210_MODE_POS = 0b00,
+ AD2S1210_MODE_VEL = 0b01,
+ AD2S1210_MODE_CONFIG = 0b11,
+};
+
+enum ad2s1210_resolution {
+ AD2S1210_RES_10 = 0b00,
+ AD2S1210_RES_12 = 0b01,
+ AD2S1210_RES_14 = 0b10,
+ AD2S1210_RES_16 = 0b11,
+};
+
+struct ad2s1210_state {
+ struct mutex lock;
+ struct spi_device *sdev;
+ /** GPIO pin connected to SAMPLE line. */
+ struct gpio_desc *sample_gpio;
+ /** GPIO pins connected to A0 and A1 lines. */
+ struct gpio_descs *mode_gpios;
+ /** Used to access config registers. */
+ struct regmap *regmap;
+ /** The external oscillator frequency in Hz. */
+ unsigned long fclkin;
+ /** The selected resolution */
+ enum ad2s1210_resolution resolution;
+ /* Scan buffer */
+ struct {
+ __be16 chan[2];
+ /* Ensure timestamp is naturally aligned. */
+ s64 timestamp __aligned(8);
+ } scan;
+ u8 rx[2] __aligned(IIO_DMA_MINALIGN);
+ u8 tx[2];
+};
+
+static int ad2s1210_set_mode(struct ad2s1210_state *st, enum ad2s1210_mode mode)
+{
+ struct gpio_descs *gpios = st->mode_gpios;
+ DECLARE_BITMAP(bitmap, 2);
+
+ bitmap[0] = mode;
+
+ return gpiod_set_array_value(gpios->ndescs, gpios->desc, gpios->info,
+ bitmap);
+}
+
+/**
+ * Writes the given data to the given register address.
+ *
+ * If the mode is configurable, the device will first be placed in
+ * configuration mode.
+ */
+static int ad2s1210_regmap_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct ad2s1210_state *st = context;
+ struct spi_transfer xfers[] = {
+ {
+ .len = 1,
+ .rx_buf = &st->rx[0],
+ .tx_buf = &st->tx[0],
+ .cs_change = 1,
+ }, {
+ .len = 1,
+ .rx_buf = &st->rx[1],
+ .tx_buf = &st->tx[1],
+ },
+ };
+ int ret;
+
+ /* values can only be 7 bits, the MSB indicates an address */
+ if (val & ~0x7F)
+ return -EINVAL;
+
+ st->tx[0] = reg;
+ st->tx[1] = val;
+
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ return spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers));
+}
+
+/**
+ * Reads value from one of the registers.
+ *
+ * If the mode is configurable, the device will first be placed in
+ * configuration mode.
+ */
+static int ad2s1210_regmap_reg_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct ad2s1210_state *st = context;
+ struct spi_transfer xfers[] = {
+ {
+ .len = 1,
+ .rx_buf = &st->rx[0],
+ .tx_buf = &st->tx[0],
+ .cs_change = 1,
+ }, {
+ .len = 1,
+ .rx_buf = &st->rx[1],
+ .tx_buf = &st->tx[1],
+ },
+ };
+ int ret;
+
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_CONFIG);
+ if (ret < 0)
+ return ret;
+
+ st->tx[0] = reg;
+ /* Must be valid register address here otherwise this could write data.
+ * It doesn't matter which one.
+ */
+ st->tx[1] = AD2S1210_REG_FAULT;
+
+ ret = spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers));
+ if (ret < 0)
+ return ret;
+
+ /* If the D7 bit is set on any read/write register, it indicates a
+ * parity error. The fault register is read-only and the D7 bit means
+ * something else there.
+ */
+ if (reg != AD2S1210_REG_FAULT && st->rx[1] & AD2S1210_ADDRESS_DATA)
+ return -EBADMSG;
+
+ *val = st->rx[1];
+
+ return 0;
+}
+
+/**
+ * Sets the excitation frequency and performs software reset.
+ *
+ * Must be called with lock held.
+ */
+static int ad2s1210_set_excitation_frequency(struct ad2s1210_state *st,
+ u16 fexcit)
+{
+ int ret;
+ u8 fcw;
+
+ fcw = fexcit * (1 << 15) / st->fclkin;
+ if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW)
+ return -ERANGE;
+
+ ret = regmap_write(st->regmap, AD2S1210_REG_EXCIT_FREQ, fcw);
+ if (ret < 0)
+ return ret;
+
+ /* software reset reinitializes the excitation frequency output */
+ return regmap_write(st->regmap, AD2S1210_REG_SOFT_RESET, 0);
+}
+
+static ssize_t excitation_frequency_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned int value;
+ u16 fexcit;
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = regmap_read(st->regmap, AD2S1210_REG_EXCIT_FREQ, &value);
+ if (ret < 0)
+ goto error_ret;
+
+ fexcit = value * st->fclkin / (1 << 15);
+
+ ret = sysfs_emit(buf, "%u\n", fexcit);
+
+error_ret:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static ssize_t excitation_frequency_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ u16 fexcit;
+ int ret;
+
+ ret = kstrtou16(buf, 10, &fexcit);
+ if (ret < 0 || fexcit < AD2S1210_MIN_EXCIT || fexcit > AD2S1210_MAX_EXCIT)
+ return -EINVAL;
+
+ mutex_lock(&st->lock);
+ ret = ad2s1210_set_excitation_frequency(st, fexcit);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = len;
+
+error_ret:
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static ssize_t phase_lock_range_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL,
+ AD2S1210_PHASE_LOCK_RANGE_44);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = sysfs_emit(buf, "%d\n", ret ? 44 : 360);
+
+error_ret:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static ssize_t phase_lock_range_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ u16 udata;
+ int ret;
+
+ ret = kstrtou16(buf, 10, &udata);
+ if (ret < 0 || (udata != 44 && udata != 360))
+ return -EINVAL;
+
+ mutex_lock(&st->lock);
+
+ ret = regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL,
+ AD2S1210_PHASE_LOCK_RANGE_44,
+ udata == 44 ? AD2S1210_PHASE_LOCK_RANGE_44 : 0);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = len;
+
+error_ret:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static ssize_t phase_lock_range_available_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "44 360\n");
+}
+
+/* read the fault register since last sample */
+static ssize_t fault_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned int value;
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &value);
+ mutex_unlock(&st->lock);
+
+ return ret < 0 ? ret : sysfs_emit(buf, "0x%02x\n", value);
+}
+
+static ssize_t fault_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned int value;
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ gpiod_set_value(st->sample_gpio, 1);
+ /* delay (2 * tck + 20) nano seconds */
+ udelay(1);
+ gpiod_set_value(st->sample_gpio, 0);
+
+ ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &value);
+ if (ret < 0)
+ goto error_ret;
+
+ gpiod_set_value(st->sample_gpio, 1);
+ gpiod_set_value(st->sample_gpio, 0);
+
+error_ret:
+ mutex_unlock(&st->lock);
+
+ return ret < 0 ? ret : len;
+}
+
+static ssize_t reg_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
+ unsigned int value;
+ int ret;
+
+ mutex_lock(&st->lock);
+ ret = regmap_read(st->regmap, iattr->address, &value);
+ mutex_unlock(&st->lock);
+
+ return ret < 0 ? ret : sysfs_emit(buf, "%d\n", value);
+}
+
+static ssize_t reg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
+ unsigned char data;
+ int ret;
+ struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
+
+ ret = kstrtou8(buf, 10, &data);
+ if (ret < 0)
+ return -EINVAL;
+
+ mutex_lock(&st->lock);
+ ret = regmap_write(st->regmap, iattr->address, data);
+ mutex_unlock(&st->lock);
+ return ret < 0 ? ret : len;
+}
+
+static const int ad2s1210_velocity_scale[] = {
+ 17089132, /* 8.192MHz / (2*pi * 2500 / 2^15) */
+ 42722830, /* 8.192MHz / (2*pi * 1000 / 2^15) */
+ 85445659, /* 8.192MHz / (2*pi * 500 / 2^15) */
+ 341782638, /* 8.192MHz / (2*pi * 125 / 2^15) */
+};
+
+static int ad2s1210_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val,
+ int *val2,
+ long mask)
+{
+ struct ad2s1210_state *st = iio_priv(indio_dev);
+ int ret = 0;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&st->lock);
+ gpiod_set_value(st->sample_gpio, 1);
+ /* delay (6 * tck + 20) nano seconds */
+ udelay(1);
+
+ switch (chan->type) {
+ case IIO_ANGL:
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_POS);
+ break;
+ case IIO_ANGL_VEL:
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_VEL);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ if (ret < 0)
+ goto error_info_raw;
+ ret = spi_read(st->sdev, st->rx, 2);
+ if (ret < 0)
+ goto error_info_raw;
+
+ switch (chan->type) {
+ case IIO_ANGL:
+ *val = be16_to_cpup((__be16 *)st->rx);
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_ANGL_VEL:
+ *val = (s16)be16_to_cpup((__be16 *)st->rx);
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+error_info_raw:
+ gpiod_set_value(st->sample_gpio, 0);
+ /* delay (2 * tck + 20) nano seconds */
+ udelay(1);
+ mutex_unlock(&st->lock);
+ break;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ANGL:
+ /* approx 0.3 arc min converted to radians */
+ *val = 0;
+ *val2 = 95874;
+ ret = IIO_VAL_INT_PLUS_NANO;
+ break;
+ case IIO_ANGL_VEL:
+ *val = st->fclkin;
+ *val2 = ad2s1210_velocity_scale[st->resolution];
+ ret = IIO_VAL_FRACTIONAL;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_ANGL:
+ mutex_lock(&st->lock);
+ ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL,
+ AD2S1210_ENABLE_HYSTERESIS);
+ if (ret < 0)
+ goto error_info_hysteresis;
+
+ *val = !!ret;
+ ret = IIO_VAL_INT;
+
+error_info_hysteresis:
+ mutex_unlock(&st->lock);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int ad2s1210_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ static const int available[] = { 0, 1 };
+ int ret = -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_ANGL:
+ *vals = available;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(available);
+ ret = IIO_AVAIL_LIST;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int ad2s1210_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ad2s1210_state *st = iio_priv(indio_dev);
+ int ret = -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_HYSTERESIS:
+ switch (chan->type) {
+ case IIO_ANGL:
+ mutex_lock(&st->lock);
+ ret = regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL,
+ AD2S1210_ENABLE_HYSTERESIS,
+ val ? AD2S1210_ENABLE_HYSTERESIS
+ : 0);
+ mutex_unlock(&st->lock);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static IIO_DEVICE_ATTR_RW(excitation_frequency, 0);
+static IIO_DEVICE_ATTR_RW(phase_lock_range, 0);
+static IIO_DEVICE_ATTR_RO(phase_lock_range_available, 0);
+static IIO_DEVICE_ATTR_RW(fault, 0);
+static IIO_DEVICE_ATTR_NAMED_RW(los_thrd, reg, AD2S1210_REG_LOS_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(dos_ovr_thrd, reg, AD2S1210_REG_DOS_OVR_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(dos_mis_thrd, reg, AD2S1210_REG_DOS_MIS_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(dos_rst_max_thrd, reg,
+ AD2S1210_REG_DOS_RST_MAX_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(dos_rst_min_thrd, reg,
+ AD2S1210_REG_DOS_RST_MIN_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(lot_high_thrd, reg, AD2S1210_REG_LOT_HIGH_THRD);
+static IIO_DEVICE_ATTR_NAMED_RW(lot_low_thrd, reg, AD2S1210_REG_LOT_LOW_THRD);
+
+static const struct iio_chan_spec ad2s1210_channels[] = {
+ {
+ .type = IIO_ANGL,
+ .indexed = 1,
+ .channel = 0,
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_HYSTERESIS),
+ .datasheet_name = "position",
+ }, {
+ .type = IIO_ANGL_VEL,
+ .indexed = 1,
+ .channel = 0,
+ .scan_index = 1,
+ .scan_type = {
+ .sign = 's',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .datasheet_name = "velocity",
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(2),
+};
+
+static struct attribute *ad2s1210_attributes[] = {
+ &iio_dev_attr_excitation_frequency.dev_attr.attr,
+ &iio_dev_attr_phase_lock_range.dev_attr.attr,
+ &iio_dev_attr_phase_lock_range_available.dev_attr.attr,
+ &iio_dev_attr_fault.dev_attr.attr,
+ &iio_dev_attr_los_thrd.dev_attr.attr,
+ &iio_dev_attr_dos_ovr_thrd.dev_attr.attr,
+ &iio_dev_attr_dos_mis_thrd.dev_attr.attr,
+ &iio_dev_attr_dos_rst_max_thrd.dev_attr.attr,
+ &iio_dev_attr_dos_rst_min_thrd.dev_attr.attr,
+ &iio_dev_attr_lot_high_thrd.dev_attr.attr,
+ &iio_dev_attr_lot_low_thrd.dev_attr.attr,
+ NULL,
+};
+
+static const struct attribute_group ad2s1210_attribute_group = {
+ .attrs = ad2s1210_attributes,
+};
+
+static int ad2s1210_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ad2s1210_state *st = iio_priv(indio_dev);
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ if (readval)
+ ret = regmap_read(st->regmap, reg, readval);
+ else
+ ret = regmap_write(st->regmap, reg, writeval);
+
+ mutex_unlock(&st->lock);
+
+ return ret;
+}
+
+static irqreturn_t ad2s1210_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad2s1210_state *st = iio_priv(indio_dev);
+ size_t chan = 0;
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ memset(&st->scan, 0, sizeof(st->scan));
+ gpiod_set_value(st->sample_gpio, 1);
+
+ if (test_bit(0, indio_dev->active_scan_mask)) {
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_POS);
+ if (ret < 0)
+ goto error_ret;
+
+ /* REVIST: we can read 3 bytes here and also get fault flags */
+ ret = spi_read(st->sdev, st->rx, 2);
+ if (ret < 0)
+ goto error_ret;
+
+ memcpy(&st->scan.chan[chan++], st->rx, 2);
+ }
+
+ if (test_bit(1, indio_dev->active_scan_mask)) {
+ ret = ad2s1210_set_mode(st, AD2S1210_MODE_VEL);
+ if (ret < 0)
+ goto error_ret;
+
+ /* REVIST: we can read 3 bytes here and also get fault flags */
+ ret = spi_read(st->sdev, st->rx, 2);
+ if (ret < 0)
+ goto error_ret;
+
+ memcpy(&st->scan.chan[chan++], st->rx, 2);
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, pf->timestamp);
+
+error_ret:
+ gpiod_set_value(st->sample_gpio, 0);
+ mutex_unlock(&st->lock);
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_info ad2s1210_info = {
+ .read_raw = ad2s1210_read_raw,
+ .read_avail = ad2s1210_read_avail,
+ .write_raw = ad2s1210_write_raw,
+ .attrs = &ad2s1210_attribute_group,
+ .debugfs_reg_access = &ad2s1210_debugfs_reg_access,
+};
+
+static int ad2s1210_setup_properties(struct ad2s1210_state *st)
+{
+ struct device *dev = &st->sdev->dev;
+ u32 val;
+ int ret;
+
+ ret = device_property_read_u32(dev, "assigned-resolution-bits", &val);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to read assigned-resolution-bits property\n");
+
+ if (val < 10 || val > 16)
+ return dev_err_probe(dev, -EINVAL,
+ "resolution out of range: %u\n", val);
+
+ st->resolution = (val - 10) >> 1;
+
+ return 0;
+}
+
+static int ad2s1210_setup_clocks(struct ad2s1210_state *st)
+{
+ struct device *dev = &st->sdev->dev;
+ struct clk *clk;
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
+
+ st->fclkin = clk_get_rate(clk);
+ if (st->fclkin < AD2S1210_MIN_CLKIN || st->fclkin > AD2S1210_MAX_CLKIN)
+ return dev_err_probe(dev, -EINVAL,
+ "clock frequency out of range: %lu\n",
+ st->fclkin);
+
+ return 0;
+}
+
+static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
+{
+ struct device *dev = &st->sdev->dev;
+ struct gpio_descs *resolution_gpios;
+ DECLARE_BITMAP(bitmap, 2);
+ int ret;
+
+ /* should not be sampling on startup */
+ st->sample_gpio = devm_gpiod_get(dev, "sample", GPIOD_OUT_LOW);
+ if (IS_ERR(st->sample_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->sample_gpio),
+ "failed to request sample GPIO\n");
+
+ /* both pins high means that we start in config mode */
+ st->mode_gpios = devm_gpiod_get_array(dev, "mode", GPIOD_OUT_HIGH);
+ if (IS_ERR(st->mode_gpios))
+ return dev_err_probe(dev, PTR_ERR(st->mode_gpios),
+ "failed to request mode GPIOs\n");
+
+ if (st->mode_gpios->ndescs != 2)
+ return dev_err_probe(dev, -EINVAL,
+ "requires exactly 2 mode-gpios\n");
+
+ /* If resolution gpios are provided, they get set to the required
+ * resolution, otherwise it is assumed the RES0 and RES1 pins are
+ * hard-wired to match the resolution indicated in the devicetree.
+ */
+ resolution_gpios = devm_gpiod_get_array_optional(dev, "resolution",
+ GPIOD_ASIS);
+ if (IS_ERR(resolution_gpios))
+ return dev_err_probe(dev, PTR_ERR(resolution_gpios),
+ "failed to request resolution GPIOs\n");
+
+ if (resolution_gpios) {
+ if (resolution_gpios->ndescs != 2)
+ return dev_err_probe(dev, -EINVAL,
+ "requires exactly 2 resolution-gpios\n");
+
+ bitmap[0] = st->resolution;
+
+ ret = gpiod_set_array_value(resolution_gpios->ndescs,
+ resolution_gpios->desc,
+ resolution_gpios->info,
+ bitmap);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to set resolution gpios\n");
+ }
+
+ return 0;
+}
+
+static const struct regmap_range ad2s1210_regmap_readable_ranges[] = {
+ regmap_reg_range(AD2S1210_REG_POSITION_MSB, AD2S1210_REG_VELOCITY_LSB),
+ regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD),
+ regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL),
+ regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT),
+};
+
+static const struct regmap_access_table ad2s1210_regmap_rd_table = {
+ .yes_ranges = ad2s1210_regmap_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_readable_ranges),
+};
+
+static const struct regmap_range ad2s1210_regmap_writeable_ranges[] = {
+ regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD),
+ regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL),
+ regmap_reg_range(AD2S1210_REG_SOFT_RESET, AD2S1210_REG_SOFT_RESET),
+ regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT),
+};
+
+static const struct regmap_access_table ad2s1210_regmap_wr_table = {
+ .yes_ranges = ad2s1210_regmap_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_writeable_ranges),
+};
+
+static int ad2s1210_setup_regmap(struct ad2s1210_state *st)
+{
+ struct device *dev = &st->sdev->dev;
+ const struct regmap_config config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .disable_locking = true,
+ .reg_read = ad2s1210_regmap_reg_read,
+ .reg_write = ad2s1210_regmap_reg_write,
+ .rd_table = &ad2s1210_regmap_rd_table,
+ .wr_table = &ad2s1210_regmap_wr_table,
+ .can_sleep = true,
+ };
+
+ st->regmap = devm_regmap_init(dev, NULL, st, &config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "failed to allocate register map\n");
+
+ return 0;
+}
+
+static int ad2s1210_init(struct ad2s1210_state *st)
+{
+ unsigned char data;
+ int ret;
+
+ mutex_lock(&st->lock);
+
+ data = AD2S1210_DEF_CONTROL & ~AD2S1210_SET_RES;
+ data |= st->resolution;
+
+ ret = regmap_write(st->regmap, AD2S1210_REG_CONTROL, data);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = ad2s1210_set_excitation_frequency(st, AD2S1210_DEF_EXCIT);
+
+error_ret:
+ mutex_unlock(&st->lock);
+ return ret;
+}
+
+static int ad2s1210_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct ad2s1210_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+
+ mutex_init(&st->lock);
+ st->sdev = spi;
+
+ ret = ad2s1210_setup_properties(st);
+ if (ret < 0)
+ return ret;
+
+ ret = ad2s1210_setup_clocks(st);
+ if (ret < 0)
+ return ret;
+
+ ret = ad2s1210_setup_gpios(st);
+ if (ret < 0)
+ return ret;
+
+ ret = ad2s1210_setup_regmap(st);
+ if (ret < 0)
+ return ret;
+
+ ret = ad2s1210_init(st);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->info = &ad2s1210_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ad2s1210_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels);
+ indio_dev->name = spi_get_device_id(spi)->name;
+
+ ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad2s1210_trigger_handler, NULL);
+ if (ret < 0)
+ return dev_err_probe(&spi->dev, ret,
+ "iio triggered buffer setup failed\n");
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ad2s1210_of_match[] = {
+ { .compatible = "adi,ad2s1210", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad2s1210_of_match);
+
+static const struct spi_device_id ad2s1210_id[] = {
+ { "ad2s1210" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad2s1210_id);
+
+static struct spi_driver ad2s1210_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .of_match_table = ad2s1210_of_match,
+ },
+ .probe = ad2s1210_probe,
+ .id_table = ad2s1210_id,
+};
+module_spi_driver(ad2s1210_driver);
+
+MODULE_AUTHOR("Graff Yang <[email protected]>");
+MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver");
+MODULE_LICENSE("GPL v2");
--
2.34.1

2023-09-21 07:20:41

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH 4/4] iio: resolver: add new driver for AD2S1210

Hi David,

kernel test robot noticed the following build warnings:

[auto build test WARNING on jic23-iio/togreg]
[also build test WARNING on linus/master v6.6-rc2 next-20230920]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url: https://github.com/intel-lab-lkp/linux/commits/David-Lechner/dt-bindings-iio-resolver-add-devicetree-bindings-for-ad2s1210/20230921-010529
base: https://git.kernel.org/pub/scm/linux/kernel/git/jic23/iio.git togreg
patch link: https://lore.kernel.org/r/20230920170253.203395-5-dlechner%40baylibre.com
patch subject: [PATCH 4/4] iio: resolver: add new driver for AD2S1210
config: sparc-allyesconfig (https://download.01.org/0day-ci/archive/20230921/[email protected]/config)
compiler: sparc64-linux-gcc (GCC) 13.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20230921/[email protected]/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All warnings (new ones prefixed by >>):

>> drivers/iio/resolver/ad2s1210.c:113: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Writes the given data to the given register address.
drivers/iio/resolver/ad2s1210.c:151: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Reads value from one of the registers.
drivers/iio/resolver/ad2s1210.c:201: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Sets the excitation frequency and performs software reset.


vim +113 drivers/iio/resolver/ad2s1210.c

111
112 /**
> 113 * Writes the given data to the given register address.
114 *
115 * If the mode is configurable, the device will first be placed in
116 * configuration mode.
117 */
118 static int ad2s1210_regmap_reg_write(void *context, unsigned int reg,
119 unsigned int val)
120 {
121 struct ad2s1210_state *st = context;
122 struct spi_transfer xfers[] = {
123 {
124 .len = 1,
125 .rx_buf = &st->rx[0],
126 .tx_buf = &st->tx[0],
127 .cs_change = 1,
128 }, {
129 .len = 1,
130 .rx_buf = &st->rx[1],
131 .tx_buf = &st->tx[1],
132 },
133 };
134 int ret;
135
136 /* values can only be 7 bits, the MSB indicates an address */
137 if (val & ~0x7F)
138 return -EINVAL;
139
140 st->tx[0] = reg;
141 st->tx[1] = val;
142
143 ret = ad2s1210_set_mode(st, AD2S1210_MODE_CONFIG);
144 if (ret < 0)
145 return ret;
146
147 return spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers));
148 }
149

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

2023-09-21 17:17:36

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 4/4] iio: resolver: add new driver for AD2S1210

On Wed, Sep 20, 2023 at 12:02:53PM -0500, David Lechner wrote:
> This adds a new driver for Analog Devices, Inc. AD2S1210 resolver to
> digital converter. The driver is based on the staging driver with the
> following improvements:
>
> Fixes:
> - Fix use before initialization bug in probe.
> - Fix not checking error returns in probe.
> - Remove spi_setup() and spi_set_drvdata() from probe.
> - Fix ordering of devm_iio_device_register()
> - Remove incorrect hysteresis logic
>
> Changes:
> - Use BIT/GENMASK macros.
> - Use devicetree to get CLKIN frequency (was sysfs attribute).
> - No longer bit-shift the raw value for IIO_CHAN_INFO_RAW.
> - Use regmap for register access.
> - Remove config sysfs attribute.
> - Use gpio array for mode and resolution gpios.
> - Invert sample gpio logic and use GPIO_ACTIVE_LOW in devicetree.
> - Change hysteresis to use IIO_CHAN_INFO_HYSTERESIS instead of custom
> device attribute.
> - Rename fexcit attribute to excitation_frequency.
> - Use devicetree to specify resolution instead of sysfs attribute.

Why? sysfs allows a user to change at run-time. DT is a firmware
change. What/who determines the resolution? Unless it's the hardware
design/designer, it probably doesn't belong in DT.

Rob

2023-09-21 20:43:04

by Conor Dooley

[permalink] [raw]
Subject: Re: [PATCH 1/4] dt-bindings: iio: resolver: add devicetree bindings for ad2s1210

Yo,

On Wed, Sep 20, 2023 at 12:02:50PM -0500, David Lechner wrote:
> This adds new DeviceTree bindings for the Analog Devices, Inc. AD2S1210
> resolver-to-digital converter.
>
> Signed-off-by: Apelete Seketeli <[email protected]>
> Signed-off-by: David Lechner <[email protected]>

Missing a From: or co-developed-by?

> ---
> .../bindings/iio/resolver/adi,ad2s1210.yaml | 150 ++++++++++++++++++
> 1 file changed, 150 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
>
> diff --git a/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml b/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
> new file mode 100644
> index 000000000000..cf6838710e52
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/resolver/adi,ad2s1210.yaml
> @@ -0,0 +1,150 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/resolver/adi,ad2s1210.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD2S1210 Resolver-to-Digital Converter
> +
> +maintainers:
> + - Michael Hennerich <[email protected]>

Why's this a chap from analog, rather than the authors?

> +description: |
> + The AD2S1210 is a complete 10-bit to 16-bit resolution tracking
> + resolver-to-digital converter, integrating an on-board programmable
> + sinusoidal oscillator that provides sine wave excitation for
> + resolvers.
> +
> + The AD2S1210 allows the user to read the angular position or the
> + angular velocity data directly from the parallel outputs or through
> + the serial interface.
> +
> + A1 A0 Result
> + 0 0 Normal mode - position output
> + 0 1 Normal mode - velocity output
> + 1 0 Reserved
> + 1 1 Configuration mode
> +
> + In normal mode, the resolution of the digital output is selected using
> + the RES0 and RES1 input pins. In configuration mode, the resolution is
> + selected by setting the RES0 and RES1 bits in the control register.
> +
> + RES1 RES0 Resolution (Bits)
> + 0 0 10
> + 0 1 12
> + 1 0 14
> + 1 1 16
> +
> + Note on SPI connections: The CS line on the AD2S1210 should hard-wired to
> + logic low and the WR/FSYNC line on the AD2S1210 should be connected to the
> + SPI CSn output of the SPI controller.
> +
> + Datasheet:
> + https://www.analog.com/media/en/technical-documentation/data-sheets/ad2s1210.pdf
> +
> +properties:
> + compatible:
> + const: adi,ad2s1210
> +
> + reg:
> + maxItems: 1
> +
> + spi-max-frequency:
> + maximum: 25000000
> +
> + spi-cpha: true
> +
> + clocks:
> + maxItems: 1
> + description: External oscillator clock (CLKIN).
> +
> + reset-gpios:
> + description:
> + GPIO connected to the /RESET pin. As the line needs to be low for the
> + reset to be active, it should be configured as GPIO_ACTIVE_LOW.
> + maxItems: 1
> +
> + sample-gpios:
> + description: |

What do you need the pipe for here? Don't see some formatting requiring
preservation. Ditto below.

> + GPIO connected to the /SAMPLE pin. As the line needs to be low to trigger
> + a sample, it should be configured as GPIO_ACTIVE_LOW.
> + maxItems: 1
> +
> + mode-gpios:
> + description: |
> + GPIO lines connected to the A0 and A1 pins. These pins select the data
> + transfer mode.
> + minItems: 2
> + maxItems: 2
> +
> + resolution-gpios:
> + description: |
> + GPIO lines connected to the RES0 and RES1 pins. These pins select the
> + resolution of the digital output. If omitted, it is assumed that the
> + RES0 and RES1 pins are hard-wired to match the assigned-resolution-bits
> + property.
> + minItems: 2
> + maxItems: 2
> +
> + fault-gpios:
> + description: |
> + GPIO lines connected to the LOT and DOS pins. These pins combined indicate
> + the type of fault present, if any. As these pins a pulled low to indicate
> + a fault condition, they should be configured as GPIO_ACTIVE_LOW.
> + minItems: 2
> + maxItems: 2
> +
> + adi,fixed-mode:
> + description: |
> + This is used to indicate the selected mode if A0 and A1 are hard-wired
> + instead of connected to GPIOS (i.e. mode-gpios is omitted).
> + $ref: /schemas/types.yaml#/definitions/string
> + enum: ["config", "velocity", "position"]

The bot is complaining about these "s not being needed.

> +
> + assigned-resolution-bits:

I figure the lack of a vendor prefix here is "inspired" by the same
property for stm32?

Cheers,
Conor.

> + description: |
> + Resolution of the digital output required by the application. This
> + determines the precision of the angle and/or the maximum speed that can
> + be measured. If resolution-gpios is omitted, it is assumed that RES0 and
> + RES1 are hard-wired to match this value.
> + enum: [10, 12, 14, 16]
> +
> +required:
> + - compatible
> + - reg
> + - spi-cpha
> + - clocks
> + - sample-gpios
> + - assigned-resolution-bits
> +
> +oneOf:
> + - required:
> + - mode-gpios
> + - required:
> + - adi,fixed-mode
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + resolver@0 {
> + compatible = "adi,ad2s1210";
> + reg = <0>;
> + spi-max-frequency = <20000000>;
> + spi-cpha;
> + clocks = <&ext_osc>;
> + sample-gpios = <&gpio0 90 GPIO_ACTIVE_LOW>;
> + mode-gpios = <&gpio0 86 0>, <&gpio0 87 0>;
> + resolution-gpios = <&gpio0 88 0>, <&gpio0 89 0>;
> + assigned-resolution-bits = <16>;
> + };
> + };
> --
> 2.34.1
>


Attachments:
(No filename) (5.99 kB)
signature.asc (235.00 B)
Download all attachments

2023-09-21 21:07:01

by Jonathan Cameron

[permalink] [raw]
Subject: Re: [PATCH 4/4] iio: resolver: add new driver for AD2S1210

On Wed, 20 Sep 2023 12:02:53 -0500
David Lechner <[email protected]> wrote:

> This adds a new driver for Analog Devices, Inc. AD2S1210 resolver to
> digital converter. The driver is based on the staging driver with the
> following improvements:
>
> Fixes:
> - Fix use before initialization bug in probe.
> - Fix not checking error returns in probe.
> - Remove spi_setup() and spi_set_drvdata() from probe.
> - Fix ordering of devm_iio_device_register()
> - Remove incorrect hysteresis logic
>
> Changes:
> - Use BIT/GENMASK macros.
> - Use devicetree to get CLKIN frequency (was sysfs attribute).
> - No longer bit-shift the raw value for IIO_CHAN_INFO_RAW.
> - Use regmap for register access.
> - Remove config sysfs attribute.
> - Use gpio array for mode and resolution gpios.
> - Invert sample gpio logic and use GPIO_ACTIVE_LOW in devicetree.
> - Change hysteresis to use IIO_CHAN_INFO_HYSTERESIS instead of custom
> device attribute.
> - Rename fexcit attribute to excitation_frequency.
> - Use devicetree to specify resolution instead of sysfs attribute.
>
> Additions:
> - Implement IIO_CHAN_INFO_SCALE.
> - Implement debugfs register access.
> - Add phase_lock_range attribute.
> - Add triggered buffer support.
>
> Signed-off-by: David Lechner <[email protected]>
Hi David,

Great to see you are looking at this one.

The way I'd prefer to see a staging graduation is not to delete the old driver
and replace it with a more fully featured driver.

Fix it up in staging then propose a move as a git mv
but format the patch with move detection disabled so that we can see
the whole code in that graduation patch.

Move the minimum first that complies with the IIO ABI and is fairly clean
then add features, possibly as a follow up patch set.

There may be existing users of this driver and they want to see the
gradual steps and chose whether to backport them all or not.

Jonathan


> ---
> .../testing/sysfs-bus-iio-resolver-ad2s1210 | 109 ++
> drivers/iio/resolver/Kconfig | 13 +
> drivers/iio/resolver/Makefile | 1 +
> drivers/iio/resolver/ad2s1210.c | 948 ++++++++++++++++++
> 4 files changed, 1071 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
> create mode 100644 drivers/iio/resolver/ad2s1210.c
>
> diff --git a/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210 b/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
> new file mode 100644
> index 000000000000..32890c85168e
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-bus-iio-resolver-ad2s1210
> @@ -0,0 +1,109 @@
> +What: /sys/bus/iio/devices/iio:deviceX/dos_mis_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Degradation of Signal Mismatch
> + Threshold value. Writing sets the value. Valid values are 0 (0V)
> + to 127 (4.826V). To convert the value to volts, multiply by
> + 0.038.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/dos_ovr_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Degradation of Signal Overrange
> + Threshold value. Writing sets the value. Valid values are 0 (0V)
> + to 127 (4.826V). To convert the value to volts, multiply by
> + 0.038.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/dos_rst_max_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Degradation of Signal Reset Maximum
> + Threshold value. Writing sets the value. Valid values are 0 (0V)
> + to 127 (4.826V). To convert the value to volts, multiply by
> + 0.038.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/dos_rst_min_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Degradation of Signal Reset Minimum
> + Threshold value. Writing sets the value. Valid values are 0 (0V)
> + to 127 (4.826V). To convert the value to volts, multiply by
> + 0.038.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/fault
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns a hex value containing the fault bit flags.
> +
> + Bit Description
> + --- -----------
> + D7 Sine/cosine inputs clipped
> + D6 Sine/cosine inputs below LOS threshold
> + D5 Sine/cosine inputs exceed DOS overrange threshold
> + D4 Sine/cosine inputs exceed DOS mismatch threshold
> + D3 Tracking error exceeds LOT threshold
> + D2 Velocity exceeds maximum tracking rate
> + D1 Phase error exceeds phase lock range
> + D0 Configuration parity error
> +
> + Writing any value will clear any fault conditions.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/excitation_frequency
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Excitation Frequency in Hz. Writing
> + sets the Excitation Frequency and performs a software reset on
> + the device to apply the change. Valid values are 2000 (2kHz) to
> + 20000 (20kHz).
> +
> +What: /sys/bus/iio/devices/iio:deviceX/los_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Loss of Signal Reset Threshold
> + value. Writing sets the value. Valid values are 0 (0V) to
> + 127 (4.826V). To convert the value to volts, multiply by 0.038.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/lot_high_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Loss of Position Tracking Detection
> + High Threshold value. Writing sets the value. Valid values are
> + 0 (0 deg) to 127 (9/18/45 deg). The interpretation of the value
> + depends on the selected resolution. To convert the value to
> + degrees, multiply by 0.35 for 10-bit resolution, multiply by
> + 0.14 for 12-bit resolution or multiply by 0.09 for 14 and 16-bit
> + resolution.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/lot_low_thrd
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Loss of Position Tracking Detection
> + Low Threshold value. Writing sets the value. Valid values are
> + 0 (0 deg) to 127 (9/18/45 deg). The interpretation of the value
> + depends on the selected resolution. To convert the value to
> + degrees, multiply by 0.35 for 10-bit resolution, multiply by
> + 0.14 for 12-bit resolution or multiply by 0.09 for 14 and 16-bit
> + resolution.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/phase_lock_range
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the current Phase lock range in degrees. Writing
> + sets the value in the configuration register.
> +
> +What: /sys/bus/iio/devices/iio:deviceX/phase_lock_range_available
> +KernelVersion: 6.7
> +Contact: [email protected]
> +Description:
> + Reading returns the possible values for the phase_lock_range
> + attribute, namely 44 and 360.
> diff --git a/drivers/iio/resolver/Kconfig b/drivers/iio/resolver/Kconfig
> index 47dbfead9b31..424529d36080 100644
> --- a/drivers/iio/resolver/Kconfig
> +++ b/drivers/iio/resolver/Kconfig
> @@ -25,4 +25,17 @@ config AD2S1200
>
> To compile this driver as a module, choose M here: the
> module will be called ad2s1200.
> +
> +config AD2S1210
> + tristate "Analog Devices ad2s1210 driver"
> + depends on SPI
> + depends on COMMON_CLK
> + depends on GPIOLIB || COMPILE_TEST
> + help
> + Say yes here to build support for Analog Devices spi resolver
> + to digital converters, ad2s1210, provides direct access via sysfs.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called ad2s1210.
> +
> endmenu
> diff --git a/drivers/iio/resolver/Makefile b/drivers/iio/resolver/Makefile
> index fa558138ce45..7f6c876c35ae 100644
> --- a/drivers/iio/resolver/Makefile
> +++ b/drivers/iio/resolver/Makefile
> @@ -5,3 +5,4 @@
>
> obj-$(CONFIG_AD2S90) += ad2s90.o
> obj-$(CONFIG_AD2S1200) += ad2s1200.o
> +obj-$(CONFIG_AD2S1210) += ad2s1210.o
> diff --git a/drivers/iio/resolver/ad2s1210.c b/drivers/iio/resolver/ad2s1210.c
> new file mode 100644
> index 000000000000..97833fbcbf7a
> --- /dev/null
> +++ b/drivers/iio/resolver/ad2s1210.c
> @@ -0,0 +1,948 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * ad2s1210.c support for the ADI Resolver to Digital Converters: AD2S1210
> + *
> + * Copyright (c) 2010-2010 Analog Devices Inc.
> + * Copyright (C) 2023 BayLibre, SAS
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include <linux/slab.h>
> +#include <linux/spi/spi.h>
> +#include <linux/sysfs.h>
> +#include <linux/types.h>
> +
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
> +
> +#define DRV_NAME "ad2s1210"
> +
> +/* default value of control register on powerup */
> +#define AD2S1210_DEF_CONTROL 0x7E
> +
> +/* control register flags */
> +#define AD2S1210_ADDRESS_DATA BIT(7)
> +#define AD2S1210_PHASE_LOCK_RANGE_44 BIT(5)
> +#define AD2S1210_ENABLE_HYSTERESIS BIT(4)
> +#define AD2S1210_SET_ENRES GENMASK(3, 2)
> +#define AD2S1210_SET_RES GENMASK(1, 0)
> +
> +#define AD2S1210_REG_POSITION_MSB 0x80
> +#define AD2S1210_REG_POSITION_LSB 0x81
> +#define AD2S1210_REG_VELOCITY_MSB 0x82
> +#define AD2S1210_REG_VELOCITY_LSB 0x83
> +#define AD2S1210_REG_LOS_THRD 0x88
> +#define AD2S1210_REG_DOS_OVR_THRD 0x89
> +#define AD2S1210_REG_DOS_MIS_THRD 0x8A
> +#define AD2S1210_REG_DOS_RST_MAX_THRD 0x8B
> +#define AD2S1210_REG_DOS_RST_MIN_THRD 0x8C
> +#define AD2S1210_REG_LOT_HIGH_THRD 0x8D
> +#define AD2S1210_REG_LOT_LOW_THRD 0x8E
> +#define AD2S1210_REG_EXCIT_FREQ 0x91
> +#define AD2S1210_REG_CONTROL 0x92
> +#define AD2S1210_REG_SOFT_RESET 0xF0
> +#define AD2S1210_REG_FAULT 0xFF
> +
> +#define AD2S1210_MIN_CLKIN 6144000
> +#define AD2S1210_MAX_CLKIN 10240000
> +#define AD2S1210_MIN_EXCIT 2000
> +#define AD2S1210_DEF_EXCIT 10000
> +#define AD2S1210_MAX_EXCIT 20000
> +#define AD2S1210_MIN_FCW 0x4
> +#define AD2S1210_MAX_FCW 0x50
> +
> +enum ad2s1210_mode {
> + AD2S1210_MODE_POS = 0b00,
> + AD2S1210_MODE_VEL = 0b01,
> + AD2S1210_MODE_CONFIG = 0b11,
> +};
> +
> +enum ad2s1210_resolution {
> + AD2S1210_RES_10 = 0b00,
> + AD2S1210_RES_12 = 0b01,
> + AD2S1210_RES_14 = 0b10,
> + AD2S1210_RES_16 = 0b11,
> +};
> +
> +struct ad2s1210_state {
> + struct mutex lock;
> + struct spi_device *sdev;
> + /** GPIO pin connected to SAMPLE line. */
> + struct gpio_desc *sample_gpio;
> + /** GPIO pins connected to A0 and A1 lines. */
> + struct gpio_descs *mode_gpios;
> + /** Used to access config registers. */
> + struct regmap *regmap;
> + /** The external oscillator frequency in Hz. */
> + unsigned long fclkin;
> + /** The selected resolution */
> + enum ad2s1210_resolution resolution;
> + /* Scan buffer */
> + struct {
> + __be16 chan[2];
> + /* Ensure timestamp is naturally aligned. */
> + s64 timestamp __aligned(8);
> + } scan;
> + u8 rx[2] __aligned(IIO_DMA_MINALIGN);
> + u8 tx[2];
> +};
> +
> +static int ad2s1210_set_mode(struct ad2s1210_state *st, enum ad2s1210_mode mode)
> +{
> + struct gpio_descs *gpios = st->mode_gpios;
> + DECLARE_BITMAP(bitmap, 2);
> +
> + bitmap[0] = mode;
> +
> + return gpiod_set_array_value(gpios->ndescs, gpios->desc, gpios->info,
> + bitmap);
> +}
> +
> +/**
> + * Writes the given data to the given register address.
> + *
> + * If the mode is configurable, the device will first be placed in
> + * configuration mode.
> + */
> +static int ad2s1210_regmap_reg_write(void *context, unsigned int reg,
> + unsigned int val)
> +{
> + struct ad2s1210_state *st = context;
> + struct spi_transfer xfers[] = {
> + {
> + .len = 1,
> + .rx_buf = &st->rx[0],
> + .tx_buf = &st->tx[0],
> + .cs_change = 1,
> + }, {
> + .len = 1,
> + .rx_buf = &st->rx[1],
> + .tx_buf = &st->tx[1],
> + },
> + };
> + int ret;
> +
> + /* values can only be 7 bits, the MSB indicates an address */
> + if (val & ~0x7F)
> + return -EINVAL;
> +
> + st->tx[0] = reg;
> + st->tx[1] = val;
> +
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_CONFIG);
> + if (ret < 0)
> + return ret;
> +
> + return spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers));
> +}
> +
> +/**
> + * Reads value from one of the registers.
> + *
> + * If the mode is configurable, the device will first be placed in
> + * configuration mode.
> + */
> +static int ad2s1210_regmap_reg_read(void *context, unsigned int reg,
> + unsigned int *val)
> +{
> + struct ad2s1210_state *st = context;
> + struct spi_transfer xfers[] = {
> + {
> + .len = 1,
> + .rx_buf = &st->rx[0],
> + .tx_buf = &st->tx[0],
> + .cs_change = 1,
> + }, {
> + .len = 1,
> + .rx_buf = &st->rx[1],
> + .tx_buf = &st->tx[1],
> + },
> + };
> + int ret;
> +
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_CONFIG);
> + if (ret < 0)
> + return ret;
> +
> + st->tx[0] = reg;
> + /* Must be valid register address here otherwise this could write data.
> + * It doesn't matter which one.
> + */
> + st->tx[1] = AD2S1210_REG_FAULT;
> +
> + ret = spi_sync_transfer(st->sdev, xfers, ARRAY_SIZE(xfers));
> + if (ret < 0)
> + return ret;
> +
> + /* If the D7 bit is set on any read/write register, it indicates a
> + * parity error. The fault register is read-only and the D7 bit means
> + * something else there.
> + */
> + if (reg != AD2S1210_REG_FAULT && st->rx[1] & AD2S1210_ADDRESS_DATA)
> + return -EBADMSG;
> +
> + *val = st->rx[1];
> +
> + return 0;
> +}
> +
> +/**
> + * Sets the excitation frequency and performs software reset.
> + *
> + * Must be called with lock held.
> + */
> +static int ad2s1210_set_excitation_frequency(struct ad2s1210_state *st,
> + u16 fexcit)
> +{
> + int ret;
> + u8 fcw;
> +
> + fcw = fexcit * (1 << 15) / st->fclkin;
> + if (fcw < AD2S1210_MIN_FCW || fcw > AD2S1210_MAX_FCW)
> + return -ERANGE;
> +
> + ret = regmap_write(st->regmap, AD2S1210_REG_EXCIT_FREQ, fcw);
> + if (ret < 0)
> + return ret;
> +
> + /* software reset reinitializes the excitation frequency output */
> + return regmap_write(st->regmap, AD2S1210_REG_SOFT_RESET, 0);
> +}
> +
> +static ssize_t excitation_frequency_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + unsigned int value;
> + u16 fexcit;
> + int ret;
> +
> + mutex_lock(&st->lock);
> + ret = regmap_read(st->regmap, AD2S1210_REG_EXCIT_FREQ, &value);
> + if (ret < 0)
> + goto error_ret;
> +
> + fexcit = value * st->fclkin / (1 << 15);
> +
> + ret = sysfs_emit(buf, "%u\n", fexcit);
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> + return ret;
> +}
> +
> +static ssize_t excitation_frequency_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + u16 fexcit;
> + int ret;
> +
> + ret = kstrtou16(buf, 10, &fexcit);
> + if (ret < 0 || fexcit < AD2S1210_MIN_EXCIT || fexcit > AD2S1210_MAX_EXCIT)
> + return -EINVAL;
> +
> + mutex_lock(&st->lock);
> + ret = ad2s1210_set_excitation_frequency(st, fexcit);
> + if (ret < 0)
> + goto error_ret;
> +
> + ret = len;
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> +
> + return ret;
> +}
> +
> +static ssize_t phase_lock_range_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + int ret;
> +
> + mutex_lock(&st->lock);
> + ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL,
> + AD2S1210_PHASE_LOCK_RANGE_44);
> + if (ret < 0)
> + goto error_ret;
> +
> + ret = sysfs_emit(buf, "%d\n", ret ? 44 : 360);
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> + return ret;
> +}
> +
> +static ssize_t phase_lock_range_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + u16 udata;
> + int ret;
> +
> + ret = kstrtou16(buf, 10, &udata);
> + if (ret < 0 || (udata != 44 && udata != 360))
> + return -EINVAL;
> +
> + mutex_lock(&st->lock);
> +
> + ret = regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL,
> + AD2S1210_PHASE_LOCK_RANGE_44,
> + udata == 44 ? AD2S1210_PHASE_LOCK_RANGE_44 : 0);
> + if (ret < 0)
> + goto error_ret;
> +
> + ret = len;
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> + return ret;
> +}
> +
> +static ssize_t phase_lock_range_available_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + return sysfs_emit(buf, "44 360\n");
> +}
> +
> +/* read the fault register since last sample */
> +static ssize_t fault_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + unsigned int value;
> + int ret;
> +
> + mutex_lock(&st->lock);
> + ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &value);
> + mutex_unlock(&st->lock);
> +
> + return ret < 0 ? ret : sysfs_emit(buf, "0x%02x\n", value);
> +}
> +
> +static ssize_t fault_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t len)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + unsigned int value;
> + int ret;
> +
> + mutex_lock(&st->lock);
> +
> + gpiod_set_value(st->sample_gpio, 1);
> + /* delay (2 * tck + 20) nano seconds */
> + udelay(1);
> + gpiod_set_value(st->sample_gpio, 0);
> +
> + ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &value);
> + if (ret < 0)
> + goto error_ret;
> +
> + gpiod_set_value(st->sample_gpio, 1);
> + gpiod_set_value(st->sample_gpio, 0);
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> +
> + return ret < 0 ? ret : len;
> +}
> +
> +static ssize_t reg_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
> + unsigned int value;
> + int ret;
> +
> + mutex_lock(&st->lock);
> + ret = regmap_read(st->regmap, iattr->address, &value);
> + mutex_unlock(&st->lock);
> +
> + return ret < 0 ? ret : sysfs_emit(buf, "%d\n", value);
> +}
> +
> +static ssize_t reg_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct ad2s1210_state *st = iio_priv(dev_to_iio_dev(dev));
> + unsigned char data;
> + int ret;
> + struct iio_dev_attr *iattr = to_iio_dev_attr(attr);
> +
> + ret = kstrtou8(buf, 10, &data);
> + if (ret < 0)
> + return -EINVAL;
> +
> + mutex_lock(&st->lock);
> + ret = regmap_write(st->regmap, iattr->address, data);
> + mutex_unlock(&st->lock);
> + return ret < 0 ? ret : len;
> +}
> +
> +static const int ad2s1210_velocity_scale[] = {
> + 17089132, /* 8.192MHz / (2*pi * 2500 / 2^15) */
> + 42722830, /* 8.192MHz / (2*pi * 1000 / 2^15) */
> + 85445659, /* 8.192MHz / (2*pi * 500 / 2^15) */
> + 341782638, /* 8.192MHz / (2*pi * 125 / 2^15) */
> +};
> +
> +static int ad2s1210_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val,
> + int *val2,
> + long mask)
> +{
> + struct ad2s1210_state *st = iio_priv(indio_dev);
> + int ret = 0;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + mutex_lock(&st->lock);
> + gpiod_set_value(st->sample_gpio, 1);
> + /* delay (6 * tck + 20) nano seconds */
> + udelay(1);
> +
> + switch (chan->type) {
> + case IIO_ANGL:
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_POS);
> + break;
> + case IIO_ANGL_VEL:
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_VEL);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + if (ret < 0)
> + goto error_info_raw;
> + ret = spi_read(st->sdev, st->rx, 2);
> + if (ret < 0)
> + goto error_info_raw;
> +
> + switch (chan->type) {
> + case IIO_ANGL:
> + *val = be16_to_cpup((__be16 *)st->rx);
> + ret = IIO_VAL_INT;
> + break;
> + case IIO_ANGL_VEL:
> + *val = (s16)be16_to_cpup((__be16 *)st->rx);
> + ret = IIO_VAL_INT;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> +error_info_raw:
> + gpiod_set_value(st->sample_gpio, 0);
> + /* delay (2 * tck + 20) nano seconds */
> + udelay(1);
> + mutex_unlock(&st->lock);
> + break;
> +
> + case IIO_CHAN_INFO_SCALE:
> + switch (chan->type) {
> + case IIO_ANGL:
> + /* approx 0.3 arc min converted to radians */
> + *val = 0;
> + *val2 = 95874;
> + ret = IIO_VAL_INT_PLUS_NANO;
> + break;
> + case IIO_ANGL_VEL:
> + *val = st->fclkin;
> + *val2 = ad2s1210_velocity_scale[st->resolution];
> + ret = IIO_VAL_FRACTIONAL;
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + break;
> +
> + case IIO_CHAN_INFO_HYSTERESIS:
> + switch (chan->type) {
> + case IIO_ANGL:
> + mutex_lock(&st->lock);
> + ret = regmap_test_bits(st->regmap, AD2S1210_REG_CONTROL,
> + AD2S1210_ENABLE_HYSTERESIS);
> + if (ret < 0)
> + goto error_info_hysteresis;
> +
> + *val = !!ret;
> + ret = IIO_VAL_INT;
> +
> +error_info_hysteresis:
> + mutex_unlock(&st->lock);
> + break;
> + default:
> + ret = -EINVAL;
> + break;
> + }
> + break;
> +
> + default:
> + ret = -EINVAL;
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int ad2s1210_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type,
> + int *length, long mask)
> +{
> + static const int available[] = { 0, 1 };
> + int ret = -EINVAL;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_HYSTERESIS:
> + switch (chan->type) {
> + case IIO_ANGL:
> + *vals = available;
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(available);
> + ret = IIO_AVAIL_LIST;
> + break;
> + default:
> + break;
> + }
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static int ad2s1210_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + struct ad2s1210_state *st = iio_priv(indio_dev);
> + int ret = -EINVAL;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_HYSTERESIS:
> + switch (chan->type) {
> + case IIO_ANGL:
> + mutex_lock(&st->lock);
> + ret = regmap_update_bits(st->regmap, AD2S1210_REG_CONTROL,
> + AD2S1210_ENABLE_HYSTERESIS,
> + val ? AD2S1210_ENABLE_HYSTERESIS
> + : 0);
> + mutex_unlock(&st->lock);
> + break;
> +
> + default:
> + break;
> + }
> + break;
> +
> + default:
> + break;
> + }
> +
> + return ret;
> +}
> +
> +static IIO_DEVICE_ATTR_RW(excitation_frequency, 0);
> +static IIO_DEVICE_ATTR_RW(phase_lock_range, 0);
> +static IIO_DEVICE_ATTR_RO(phase_lock_range_available, 0);
> +static IIO_DEVICE_ATTR_RW(fault, 0);
> +static IIO_DEVICE_ATTR_NAMED_RW(los_thrd, reg, AD2S1210_REG_LOS_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(dos_ovr_thrd, reg, AD2S1210_REG_DOS_OVR_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(dos_mis_thrd, reg, AD2S1210_REG_DOS_MIS_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(dos_rst_max_thrd, reg,
> + AD2S1210_REG_DOS_RST_MAX_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(dos_rst_min_thrd, reg,
> + AD2S1210_REG_DOS_RST_MIN_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(lot_high_thrd, reg, AD2S1210_REG_LOT_HIGH_THRD);
> +static IIO_DEVICE_ATTR_NAMED_RW(lot_low_thrd, reg, AD2S1210_REG_LOT_LOW_THRD);
> +
> +static const struct iio_chan_spec ad2s1210_channels[] = {
> + {
> + .type = IIO_ANGL,
> + .indexed = 1,
> + .channel = 0,
> + .scan_index = 0,
> + .scan_type = {
> + .sign = 'u',
> + .realbits = 16,
> + .storagebits = 16,
> + .endianness = IIO_BE,
> + },
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE) |
> + BIT(IIO_CHAN_INFO_HYSTERESIS),
> + .info_mask_separate_available =
> + BIT(IIO_CHAN_INFO_HYSTERESIS),
> + .datasheet_name = "position",
> + }, {
> + .type = IIO_ANGL_VEL,
> + .indexed = 1,
> + .channel = 0,
> + .scan_index = 1,
> + .scan_type = {
> + .sign = 's',
> + .realbits = 16,
> + .storagebits = 16,
> + .endianness = IIO_BE,
> + },
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SCALE),
> + .datasheet_name = "velocity",
> + },
> + IIO_CHAN_SOFT_TIMESTAMP(2),
> +};
> +
> +static struct attribute *ad2s1210_attributes[] = {
> + &iio_dev_attr_excitation_frequency.dev_attr.attr,
> + &iio_dev_attr_phase_lock_range.dev_attr.attr,
> + &iio_dev_attr_phase_lock_range_available.dev_attr.attr,
> + &iio_dev_attr_fault.dev_attr.attr,
> + &iio_dev_attr_los_thrd.dev_attr.attr,
> + &iio_dev_attr_dos_ovr_thrd.dev_attr.attr,
> + &iio_dev_attr_dos_mis_thrd.dev_attr.attr,
> + &iio_dev_attr_dos_rst_max_thrd.dev_attr.attr,
> + &iio_dev_attr_dos_rst_min_thrd.dev_attr.attr,
> + &iio_dev_attr_lot_high_thrd.dev_attr.attr,
> + &iio_dev_attr_lot_low_thrd.dev_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group ad2s1210_attribute_group = {
> + .attrs = ad2s1210_attributes,
> +};
> +
> +static int ad2s1210_debugfs_reg_access(struct iio_dev *indio_dev,
> + unsigned int reg, unsigned int writeval,
> + unsigned int *readval)
> +{
> + struct ad2s1210_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + mutex_lock(&st->lock);
> +
> + if (readval)
> + ret = regmap_read(st->regmap, reg, readval);
> + else
> + ret = regmap_write(st->regmap, reg, writeval);
> +
> + mutex_unlock(&st->lock);
> +
> + return ret;
> +}
> +
> +static irqreturn_t ad2s1210_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ad2s1210_state *st = iio_priv(indio_dev);
> + size_t chan = 0;
> + int ret;
> +
> + mutex_lock(&st->lock);
> +
> + memset(&st->scan, 0, sizeof(st->scan));
> + gpiod_set_value(st->sample_gpio, 1);
> +
> + if (test_bit(0, indio_dev->active_scan_mask)) {
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_POS);
> + if (ret < 0)
> + goto error_ret;
> +
> + /* REVIST: we can read 3 bytes here and also get fault flags */
> + ret = spi_read(st->sdev, st->rx, 2);
> + if (ret < 0)
> + goto error_ret;
> +
> + memcpy(&st->scan.chan[chan++], st->rx, 2);
> + }
> +
> + if (test_bit(1, indio_dev->active_scan_mask)) {
> + ret = ad2s1210_set_mode(st, AD2S1210_MODE_VEL);
> + if (ret < 0)
> + goto error_ret;
> +
> + /* REVIST: we can read 3 bytes here and also get fault flags */
> + ret = spi_read(st->sdev, st->rx, 2);
> + if (ret < 0)
> + goto error_ret;
> +
> + memcpy(&st->scan.chan[chan++], st->rx, 2);
> + }
> +
> + iio_push_to_buffers_with_timestamp(indio_dev, &st->scan, pf->timestamp);
> +
> +error_ret:
> + gpiod_set_value(st->sample_gpio, 0);
> + mutex_unlock(&st->lock);
> + iio_trigger_notify_done(indio_dev->trig);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct iio_info ad2s1210_info = {
> + .read_raw = ad2s1210_read_raw,
> + .read_avail = ad2s1210_read_avail,
> + .write_raw = ad2s1210_write_raw,
> + .attrs = &ad2s1210_attribute_group,
> + .debugfs_reg_access = &ad2s1210_debugfs_reg_access,
> +};
> +
> +static int ad2s1210_setup_properties(struct ad2s1210_state *st)
> +{
> + struct device *dev = &st->sdev->dev;
> + u32 val;
> + int ret;
> +
> + ret = device_property_read_u32(dev, "assigned-resolution-bits", &val);
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "failed to read assigned-resolution-bits property\n");
> +
> + if (val < 10 || val > 16)
> + return dev_err_probe(dev, -EINVAL,
> + "resolution out of range: %u\n", val);
> +
> + st->resolution = (val - 10) >> 1;
> +
> + return 0;
> +}
> +
> +static int ad2s1210_setup_clocks(struct ad2s1210_state *st)
> +{
> + struct device *dev = &st->sdev->dev;
> + struct clk *clk;
> +
> + clk = devm_clk_get_enabled(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk), "failed to get clock\n");
> +
> + st->fclkin = clk_get_rate(clk);
> + if (st->fclkin < AD2S1210_MIN_CLKIN || st->fclkin > AD2S1210_MAX_CLKIN)
> + return dev_err_probe(dev, -EINVAL,
> + "clock frequency out of range: %lu\n",
> + st->fclkin);
> +
> + return 0;
> +}
> +
> +static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
> +{
> + struct device *dev = &st->sdev->dev;
> + struct gpio_descs *resolution_gpios;
> + DECLARE_BITMAP(bitmap, 2);
> + int ret;
> +
> + /* should not be sampling on startup */
> + st->sample_gpio = devm_gpiod_get(dev, "sample", GPIOD_OUT_LOW);
> + if (IS_ERR(st->sample_gpio))
> + return dev_err_probe(dev, PTR_ERR(st->sample_gpio),
> + "failed to request sample GPIO\n");
> +
> + /* both pins high means that we start in config mode */
> + st->mode_gpios = devm_gpiod_get_array(dev, "mode", GPIOD_OUT_HIGH);
> + if (IS_ERR(st->mode_gpios))
> + return dev_err_probe(dev, PTR_ERR(st->mode_gpios),
> + "failed to request mode GPIOs\n");
> +
> + if (st->mode_gpios->ndescs != 2)
> + return dev_err_probe(dev, -EINVAL,
> + "requires exactly 2 mode-gpios\n");
> +
> + /* If resolution gpios are provided, they get set to the required
> + * resolution, otherwise it is assumed the RES0 and RES1 pins are
> + * hard-wired to match the resolution indicated in the devicetree.
> + */
> + resolution_gpios = devm_gpiod_get_array_optional(dev, "resolution",
> + GPIOD_ASIS);
> + if (IS_ERR(resolution_gpios))
> + return dev_err_probe(dev, PTR_ERR(resolution_gpios),
> + "failed to request resolution GPIOs\n");
> +
> + if (resolution_gpios) {
> + if (resolution_gpios->ndescs != 2)
> + return dev_err_probe(dev, -EINVAL,
> + "requires exactly 2 resolution-gpios\n");
> +
> + bitmap[0] = st->resolution;
> +
> + ret = gpiod_set_array_value(resolution_gpios->ndescs,
> + resolution_gpios->desc,
> + resolution_gpios->info,
> + bitmap);
> + if (ret < 0)
> + return dev_err_probe(dev, ret,
> + "failed to set resolution gpios\n");
> + }
> +
> + return 0;
> +}
> +
> +static const struct regmap_range ad2s1210_regmap_readable_ranges[] = {
> + regmap_reg_range(AD2S1210_REG_POSITION_MSB, AD2S1210_REG_VELOCITY_LSB),
> + regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD),
> + regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL),
> + regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT),
> +};
> +
> +static const struct regmap_access_table ad2s1210_regmap_rd_table = {
> + .yes_ranges = ad2s1210_regmap_readable_ranges,
> + .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_readable_ranges),
> +};
> +
> +static const struct regmap_range ad2s1210_regmap_writeable_ranges[] = {
> + regmap_reg_range(AD2S1210_REG_LOS_THRD, AD2S1210_REG_LOT_LOW_THRD),
> + regmap_reg_range(AD2S1210_REG_EXCIT_FREQ, AD2S1210_REG_CONTROL),
> + regmap_reg_range(AD2S1210_REG_SOFT_RESET, AD2S1210_REG_SOFT_RESET),
> + regmap_reg_range(AD2S1210_REG_FAULT, AD2S1210_REG_FAULT),
> +};
> +
> +static const struct regmap_access_table ad2s1210_regmap_wr_table = {
> + .yes_ranges = ad2s1210_regmap_writeable_ranges,
> + .n_yes_ranges = ARRAY_SIZE(ad2s1210_regmap_writeable_ranges),
> +};
> +
> +static int ad2s1210_setup_regmap(struct ad2s1210_state *st)
> +{
> + struct device *dev = &st->sdev->dev;
> + const struct regmap_config config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .disable_locking = true,
> + .reg_read = ad2s1210_regmap_reg_read,
> + .reg_write = ad2s1210_regmap_reg_write,
> + .rd_table = &ad2s1210_regmap_rd_table,
> + .wr_table = &ad2s1210_regmap_wr_table,
> + .can_sleep = true,
> + };
> +
> + st->regmap = devm_regmap_init(dev, NULL, st, &config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "failed to allocate register map\n");
> +
> + return 0;
> +}
> +
> +static int ad2s1210_init(struct ad2s1210_state *st)
> +{
> + unsigned char data;
> + int ret;
> +
> + mutex_lock(&st->lock);
> +
> + data = AD2S1210_DEF_CONTROL & ~AD2S1210_SET_RES;
> + data |= st->resolution;
> +
> + ret = regmap_write(st->regmap, AD2S1210_REG_CONTROL, data);
> + if (ret < 0)
> + goto error_ret;
> +
> + ret = ad2s1210_set_excitation_frequency(st, AD2S1210_DEF_EXCIT);
> +
> +error_ret:
> + mutex_unlock(&st->lock);
> + return ret;
> +}
> +
> +static int ad2s1210_probe(struct spi_device *spi)
> +{
> + struct iio_dev *indio_dev;
> + struct ad2s1210_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> +
> + mutex_init(&st->lock);
> + st->sdev = spi;
> +
> + ret = ad2s1210_setup_properties(st);
> + if (ret < 0)
> + return ret;
> +
> + ret = ad2s1210_setup_clocks(st);
> + if (ret < 0)
> + return ret;
> +
> + ret = ad2s1210_setup_gpios(st);
> + if (ret < 0)
> + return ret;
> +
> + ret = ad2s1210_setup_regmap(st);
> + if (ret < 0)
> + return ret;
> +
> + ret = ad2s1210_init(st);
> + if (ret < 0)
> + return ret;
> +
> + indio_dev->info = &ad2s1210_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->channels = ad2s1210_channels;
> + indio_dev->num_channels = ARRAY_SIZE(ad2s1210_channels);
> + indio_dev->name = spi_get_device_id(spi)->name;
> +
> + ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad2s1210_trigger_handler, NULL);
> + if (ret < 0)
> + return dev_err_probe(&spi->dev, ret,
> + "iio triggered buffer setup failed\n");
> +
> + return devm_iio_device_register(&spi->dev, indio_dev);
> +}
> +
> +static const struct of_device_id ad2s1210_of_match[] = {
> + { .compatible = "adi,ad2s1210", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, ad2s1210_of_match);
> +
> +static const struct spi_device_id ad2s1210_id[] = {
> + { "ad2s1210" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, ad2s1210_id);
> +
> +static struct spi_driver ad2s1210_driver = {
> + .driver = {
> + .name = DRV_NAME,
> + .of_match_table = ad2s1210_of_match,
> + },
> + .probe = ad2s1210_probe,
> + .id_table = ad2s1210_id,
> +};
> +module_spi_driver(ad2s1210_driver);
> +
> +MODULE_AUTHOR("Graff Yang <[email protected]>");
> +MODULE_DESCRIPTION("Analog Devices AD2S1210 Resolver to Digital SPI driver");
> +MODULE_LICENSE("GPL v2");

2023-09-21 22:19:38

by David Lechner

[permalink] [raw]
Subject: Re: [PATCH 1/4] dt-bindings: iio: resolver: add devicetree bindings for ad2s1210

On Thu, Sep 21, 2023 at 11:06 AM Conor Dooley <[email protected]> wrote:
>
> > Signed-off-by: Apelete Seketeli <[email protected]>
> > Signed-off-by: David Lechner <[email protected]>
>
> Missing a From: or co-developed-by?

Missing Co-developed-by:. Will fix.

> > +maintainers:
> > + - Michael Hennerich <[email protected]>
>
> Why's this a chap from analog, rather than the authors?

This was written by my codeveloper so not sure about the why. Michael is
on the CC so he can clarify if this is not correct.

> > + sample-gpios:
> > + description: |
>
> What do you need the pipe for here? Don't see some formatting requiring
> preservation. Ditto below.
>

It is just a habit to always do this for multi-line text. Will remove.

> > +
> > + assigned-resolution-bits:
>
> I figure the lack of a vendor prefix here is "inspired" by the same
> property for stm32?
>

Correct.

2023-09-22 13:15:41

by David Lechner

[permalink] [raw]
Subject: Re: [PATCH 4/4] iio: resolver: add new driver for AD2S1210

On Thu, Sep 21, 2023 at 5:45 PM Rob Herring <[email protected]> wrote:
>
> On Wed, Sep 20, 2023 at 12:02:53PM -0500, David Lechner wrote:
> > This adds a new driver for Analog Devices, Inc. AD2S1210 resolver to
> > digital converter. The driver is based on the staging driver with the
> > following improvements:
> >

...

> > - Use devicetree to specify resolution instead of sysfs attribute.
>
> Why? sysfs allows a user to change at run-time. DT is a firmware
> change. What/who determines the resolution? Unless it's the hardware
> design/designer, it probably doesn't belong in DT.
>

This one is a bit of a gray area since it could be either way. The chip
has two input pins for selecting the resolution. If the hardware
designer decides to connect these to gpios, then they could be changed
at runtime. But if the hardware designer decides to hard-wire these pins
then we need to supply this information via the devicetree.

The choice of the resolution is determined by the physical requirements
of the full system. This device measures rotational position and velocity.
So the main reason to pick something less than the full 16-bit resolution is
if the max rotational speed of whatever is connected is going to be greater
than 125 revolutions per second (assuming 8.192 MHz clock).