[PATCH v3 2/2] drivers: misc: adi-axi-tdd: Add TDD engine

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch introduces the driver for the new ADI TDD engine HDL.
The generic TDD controller is in essence a waveform generator
capable of addressing RF applications which require Time Division
Duplexing, as well as controlling other modules of general
applications through its dedicated 32 channel outputs.

Signed-off-by: Eliza Balas <eliza.balas@xxxxxxxxxx>
---
 .../sysfs-bus-platform-drivers-adi-axi-tdd    | 156 ++++
 MAINTAINERS                                   |   2 +
 drivers/misc/Kconfig                          |  10 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/adi-axi-tdd.c                    | 780 ++++++++++++++++++
 5 files changed, 949 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-bus-platform-drivers-adi-axi-tdd
 create mode 100644 drivers/misc/adi-axi-tdd.c

diff --git a/Documentation/ABI/testing/sysfs-bus-platform-drivers-adi-axi-tdd b/Documentation/ABI/testing/sysfs-bus-platform-drivers-adi-axi-tdd
new file mode 100644
index 000000000000..e7f58ec41452
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-platform-drivers-adi-axi-tdd
@@ -0,0 +1,156 @@
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/burst_count
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the number of TDD frames per burst.
+                If set to 0x0 and the TDD module is enabled, then the controller
+                operates in TDD mode as long as the ENABLE property is set.
+                If set to a non-zero value, the controller
+                operates for the set number of frames and then stops.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/core_id
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Displays the value of the ID configuration parameter.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/enable
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to enable or disable the TDD module.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/frame_length_ms
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the length of the transmission frame,
+                defined in milliseconds.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/frame_length_raw
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the length of the transmission frame,
+                defined in clock cycles of the input clock.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/internal_sync_period_ms
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the period of the internal sync generator,
+                defined in milliseconds.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/internal_sync_period_raw
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the period of the internal sync generator,
+                defined in clock cycles of the input clock.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/magic
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Displays the code name of the TDD module for identification.
+                The value is 0x5444444E ('T', 'D', 'D', 'N').
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_enable
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to enable or disable the output channel CH<n>.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_off_ms
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the offset from the beginning of a new
+                frame when channel CH<n> is reset, defined in milliseconds.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_off_raw
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the offset from the beginning of a new
+                frame when channel CH<n> is reset, defined in clock cycles
+                of the input clock.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_on_ms
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the offset from the beginning of a new
+                frame when channel CH<n> is set, defined in milliseconds.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_on_raw
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the offset from the beginning of a new
+                frame when channel CH<n> is set, defined in clock cycles
+                of the input clock.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/out_channel<n>_polarity
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the polarity of the output channel CH<n>.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/scratch
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to write and read the scratch register.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/startup_delay_ms
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the initial delay before the first frame,
+                defined in milliseconds.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/startup_delay_raw
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to set the initial delay before the first frame,
+                defined in clock cycles of the input clock.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/state
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Displays the current state of the peripheral FSM.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/sync_external
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to enable or disable the external sync trigger.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/sync_internal
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to enable or disable the internal sync trigger.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/sync_reset
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to reset the internal counter when receiving a
+                sync event.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/sync_soft
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Allows the user to trigger one software sync pulse.
+
+What:           /sys/bus/platform/drivers/adi-axi-tdd/*/version
+Date:           November 2023
+KernelVersion:  6.7
+Contact:        Eliza Balas <eliza.balas@xxxxxxxxxx>
+Description:    Displays the version of the peripheral. Follows semantic
+                versioning. Current version is 2.00.61.
diff --git a/MAINTAINERS b/MAINTAINERS
index 13a1e1990c19..51b87d90d3bc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1349,7 +1349,9 @@ M:	Eliza Balas <eliza.balas@xxxxxxxxxx>
 S:	Supported
 W:	http://wiki.analog.com/resources/fpga/docs/axi_tdd
 W:	http://ez.analog.com/linux-software-drivers/
+F:	Documentation/ABI/testing/sysfs-bus-platform-drivers-adi-axi-tdd
 F:	Documentation/devicetree/bindings/misc/adi,axi-tdd.yaml
+F:	drivers/misc/adi-axi-tdd.c
 
 ANALOG DEVICES INC IIO DRIVERS
 M:	Lars-Peter Clausen <lars@xxxxxxxxxx>
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index cadd4a820c03..45074a93fa55 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -50,6 +50,16 @@ config AD525X_DPOT_SPI
 	  To compile this driver as a module, choose M here: the
 	  module will be called ad525x_dpot-spi.
 
+config ADI_AXI_TDD
+	tristate "Analog Devices TDD Engine support"
+	depends on HAS_IOMEM
+	select REGMAP_MMIO
+	help
+	  The ADI AXI TDD core is the reworked and generic TDD engine which
+	  was developed for general use in Analog Devices HDL projects. Unlike
+	  the previous TDD engine, this core can only be used standalone mode,
+	  and is not embedded into other devices.
+
 config DUMMY_IRQ
 	tristate "Dummy IRQ handler"
 	help
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f2a4d1ff65d4..d97afe1eeba5 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_IBMVMC)		+= ibmvmc.o
 obj-$(CONFIG_AD525X_DPOT)	+= ad525x_dpot.o
 obj-$(CONFIG_AD525X_DPOT_I2C)	+= ad525x_dpot-i2c.o
 obj-$(CONFIG_AD525X_DPOT_SPI)	+= ad525x_dpot-spi.o
+obj-$(CONFIG_ADI_AXI_TDD)	+= adi-axi-tdd.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
 obj-$(CONFIG_DUMMY_IRQ)		+= dummy-irq.o
 obj-$(CONFIG_ICS932S401)	+= ics932s401.o
diff --git a/drivers/misc/adi-axi-tdd.c b/drivers/misc/adi-axi-tdd.c
new file mode 100644
index 000000000000..94ad0f805c55
--- /dev/null
+++ b/drivers/misc/adi-axi-tdd.c
@@ -0,0 +1,780 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TDD HDL CORE driver
+ *
+ * Copyright 2023 Analog Devices Inc.
+ *
+ */
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/fpga/adi-axi-common.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+/* Register Map */
+#define ADI_REG_TDD_VERSION                 0x0000
+#define ADI_REG_TDD_PERIPHERAL_ID           0x0004
+#define ADI_REG_TDD_SCRATCH                 0x0008
+#define ADI_REG_TDD_IDENTIFICATION          0x000c
+#define ADI_REG_TDD_INTERFACE_DESCRIPTION   0x0010
+#define ADI_REG_TDD_DEFAULT_POLARITY        0x0014
+#define ADI_REG_TDD_CONTROL                 0x0040
+#define ADI_REG_TDD_CHANNEL_ENABLE          0x0044
+#define ADI_REG_TDD_CHANNEL_POLARITY        0x0048
+#define ADI_REG_TDD_BURST_COUNT             0x004c
+#define ADI_REG_TDD_STARTUP_DELAY           0x0050
+#define ADI_REG_TDD_FRAME_LENGTH            0x0054
+#define ADI_REG_TDD_SYNC_COUNTER_LOW        0x0058
+#define ADI_REG_TDD_SYNC_COUNTER_HIGH       0x005c
+#define ADI_REG_TDD_STATUS                  0x0060
+#define ADI_REG_TDD_CHANNEL_BASE            0x0080
+
+/* Identification Register */
+#define ADI_TDD_MAGIC                       0x5444444E
+
+/* Interface Description Register */
+#define ADI_TDD_SYNC_COUNT_WIDTH            GENMASK(30, 24)
+#define ADI_TDD_BURST_COUNT_WIDTH           GENMASK(21, 16)
+#define ADI_TDD_REG_WIDTH                   GENMASK(13, 8)
+#define ADI_TDD_SYNC_EXTERNAL_CDC           BIT(7)
+#define ADI_TDD_SYNC_EXTERNAL               BIT(6)
+#define ADI_TDD_SYNC_INTERNAL               BIT(5)
+#define ADI_TDD_CHANNEL_COUNT               GENMASK(4, 0)
+
+/* Control Register */
+#define ADI_TDD_SYNC_SOFT                   BIT(4)
+#define ADI_TDD_SYNC_EXT                    BIT(3)
+#define ADI_TDD_SYNC_INT                    BIT(2)
+#define ADI_TDD_SYNC_RST                    BIT(1)
+#define ADI_TDD_ENABLE                      BIT(0)
+
+/* Channel Definitions */
+#define ADI_TDD_CHANNEL_OFFSET              0x0008
+#define ADI_TDD_CHANNEL_ON                  0x0000
+#define ADI_TDD_CHANNEL_OFF                 0x0004
+
+#define ADI_REG_TDD_CHANNEL(c, o)           \
+	(ADI_REG_TDD_CHANNEL_BASE + ADI_TDD_CHANNEL_OFFSET * (c) + (o))
+
+enum adi_axi_tdd_attribute_id {
+	ADI_TDD_ATTR_VERSION,
+	ADI_TDD_ATTR_CORE_ID,
+	ADI_TDD_ATTR_SCRATCH,
+	ADI_TDD_ATTR_MAGIC,
+
+	ADI_TDD_ATTR_SYNC_SOFT,
+	ADI_TDD_ATTR_SYNC_EXT,
+	ADI_TDD_ATTR_SYNC_INT,
+	ADI_TDD_ATTR_SYNC_RST,
+	ADI_TDD_ATTR_ENABLE,
+
+	ADI_TDD_ATTR_BURST_COUNT,
+	ADI_TDD_ATTR_STARTUP_DELAY_RAW,
+	ADI_TDD_ATTR_STARTUP_DELAY_MS,
+	ADI_TDD_ATTR_FRAME_LENGTH_RAW,
+	ADI_TDD_ATTR_FRAME_LENGTH_MS,
+	ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_RAW,
+	ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_MS,
+
+	ADI_TDD_ATTR_STATE,
+
+	ADI_TDD_ATTR_CHANNEL_ENABLE,
+	ADI_TDD_ATTR_CHANNEL_POLARITY,
+	ADI_TDD_ATTR_CHANNEL_ON_RAW,
+	ADI_TDD_ATTR_CHANNEL_OFF_RAW,
+	ADI_TDD_ATTR_CHANNEL_ON_MS,
+	ADI_TDD_ATTR_CHANNEL_OFF_MS,
+};
+
+struct adi_axi_tdd_clk {
+	struct notifier_block nb;
+	unsigned long rate;
+	struct clk *clk;
+
+};
+
+struct adi_axi_tdd_attribute {
+	enum adi_axi_tdd_attribute_id id;
+	struct device_attribute attr;
+	u32 channel;
+	u8 name[32];
+};
+
+#define to_tdd_attribute(x) container_of(x, struct adi_axi_tdd_attribute, attr)
+
+/**
+ * struct adi_axi_tdd_state - Driver state information for the TDD CORE
+ * @clk: Interface clock definition. Used to translate ms into cycle counts
+ * @base: Device register base address in memory
+ * @regs: Device memory-mapped region regmap
+ * @sync_count_width: Bit width of the internal synchronization counter, <= 64
+ * @sync_count_mask: Bit mask of sync counter
+ * @burst_count_width: Bit width of the burst counter, <= 32
+ * @burst_count_mask: Bit mask of the burst counter
+ * @reg_width: Timing register bit width, <= 32
+ * @reg_mask: Timing register bit mask
+ * @sync_external_cdc: Whether the external sync input is synchronized into the main clock domain
+ * @sync_external: Whether external synchronization support was enabled at synthesis time
+ * @sync_internal: Whether internal synchronization support was enabled at synthesis time
+ * @channel_count: Available channel count
+ * @enabled: Whether the TDD engine is currently enabled.
+ *	     Note: Most configuration registers cannot be changed while the TDD core is enabled.
+ */
+struct adi_axi_tdd_state {
+	struct adi_axi_tdd_clk clk;
+	void __iomem *base;
+	struct regmap *regs;
+	u32 sync_count_width;
+	u64 sync_count_mask;
+	u32 burst_count_width;
+	u32 burst_count_mask;
+	u32 reg_width;
+	u32 reg_mask;
+	bool sync_external_cdc;
+	bool sync_external;
+	bool sync_internal;
+	u32 channel_count;
+	bool enabled;
+};
+
+static const struct regmap_config adi_axi_tdd_regmap_cfg = {
+	.name = "adi-axi-tdd",
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+};
+
+static int adi_axi_tdd_format_ms(struct adi_axi_tdd_state *st, u64 x, char *buf)
+{
+	u32 vals[2];
+	u64 t_ns;
+
+	t_ns = div_u64(x * 1000000000ULL, READ_ONCE(st->clk.rate));
+	vals[0] = div_u64_rem(t_ns, 1000000, &vals[1]);
+
+	return sysfs_emit(buf, "%d.%06u\n", vals[0], vals[1]);
+}
+
+static ssize_t adi_axi_tdd_show(struct device *dev,
+				struct device_attribute *dev_attr, char *buf)
+{
+	const struct adi_axi_tdd_attribute *attr = to_tdd_attribute(dev_attr);
+	struct adi_axi_tdd_state *st = dev_get_drvdata(dev);
+	u32 channel = attr->channel;
+	bool ms = false;
+	u64 data64;
+	u32 data;
+	int ret;
+
+	switch (attr->id) {
+	case ADI_TDD_ATTR_VERSION:
+		ret = regmap_read(st->regs, ADI_AXI_REG_VERSION, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d.%.2d.%c\n",
+				 ADI_AXI_PCORE_VER_MAJOR(data),
+				 ADI_AXI_PCORE_VER_MINOR(data),
+				 ADI_AXI_PCORE_VER_PATCH(data));
+	case ADI_TDD_ATTR_CORE_ID:
+		ret = regmap_read(st->regs, ADI_REG_TDD_PERIPHERAL_ID, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_SCRATCH:
+		ret = regmap_read(st->regs, ADI_REG_TDD_SCRATCH, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "0x%08x\n", data);
+	case ADI_TDD_ATTR_MAGIC:
+		ret = regmap_read(st->regs, ADI_REG_TDD_IDENTIFICATION, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "0x%08x\n", data);
+	case ADI_TDD_ATTR_SYNC_EXT:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CONTROL, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d\n", !!(data & ADI_TDD_SYNC_EXT));
+	case ADI_TDD_ATTR_SYNC_INT:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CONTROL, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d\n", !!(data & ADI_TDD_SYNC_INT));
+	case ADI_TDD_ATTR_SYNC_RST:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CONTROL, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d\n", !!(data & ADI_TDD_SYNC_RST));
+	case ADI_TDD_ATTR_ENABLE:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CONTROL, &data);
+		if (ret)
+			return ret;
+		st->enabled = !!(data & ADI_TDD_ENABLE);
+		return sysfs_emit(buf, "%d\n", st->enabled);
+	case ADI_TDD_ATTR_BURST_COUNT:
+		ret = regmap_read(st->regs, ADI_REG_TDD_BURST_COUNT, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_STARTUP_DELAY_RAW:
+		ret = regmap_read(st->regs, ADI_REG_TDD_STARTUP_DELAY, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_STARTUP_DELAY_MS:
+		ret = regmap_read(st->regs, ADI_REG_TDD_STARTUP_DELAY, &data);
+		if (ret)
+			return ret;
+		return adi_axi_tdd_format_ms(st, data, buf);
+	case ADI_TDD_ATTR_FRAME_LENGTH_RAW:
+		ret = regmap_read(st->regs, ADI_REG_TDD_FRAME_LENGTH, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_FRAME_LENGTH_MS:
+		ret = regmap_read(st->regs, ADI_REG_TDD_FRAME_LENGTH, &data);
+		if (ret)
+			return ret;
+		return adi_axi_tdd_format_ms(st, data, buf);
+	case ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_RAW:
+		ret = regmap_bulk_read(st->regs, ADI_REG_TDD_SYNC_COUNTER_LOW,
+				       &data64, 2);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%llu\n", data64);
+	case ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_MS:
+		ret = regmap_bulk_read(st->regs, ADI_REG_TDD_SYNC_COUNTER_LOW,
+				       &data64, 2);
+		if (ret)
+			return ret;
+		return adi_axi_tdd_format_ms(st, data64, buf);
+	case ADI_TDD_ATTR_STATE:
+		ret = regmap_read(st->regs, ADI_REG_TDD_STATUS, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_CHANNEL_ENABLE:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CHANNEL_ENABLE, &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d\n", !!(BIT(channel) & data));
+	case ADI_TDD_ATTR_CHANNEL_POLARITY:
+		ret = regmap_read(st->regs, ADI_REG_TDD_CHANNEL_POLARITY,
+				  &data);
+		if (ret)
+			return ret;
+		return sysfs_emit(buf, "%d\n", !!(BIT(channel) & data));
+	case ADI_TDD_ATTR_CHANNEL_ON_MS:
+		ms = true;
+		fallthrough;
+	case ADI_TDD_ATTR_CHANNEL_ON_RAW:
+		ret = regmap_read(st->regs,
+				  ADI_REG_TDD_CHANNEL(channel,
+						      ADI_TDD_CHANNEL_ON),
+				  &data);
+		if (ret)
+			return ret;
+		if (ms)
+			return adi_axi_tdd_format_ms(st, data, buf);
+		return sysfs_emit(buf, "%u\n", data);
+	case ADI_TDD_ATTR_CHANNEL_OFF_MS:
+		ms = true;
+		fallthrough;
+	case ADI_TDD_ATTR_CHANNEL_OFF_RAW:
+		ret = regmap_read(st->regs,
+				  ADI_REG_TDD_CHANNEL(channel,
+						      ADI_TDD_CHANNEL_OFF),
+				  &data);
+		if (ret)
+			return ret;
+		if (ms)
+			return adi_axi_tdd_format_ms(st, data, buf);
+		return sysfs_emit(buf, "%u\n", data);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int adi_axi_tdd_parse_ms(struct adi_axi_tdd_state *st,
+				const char *buf,
+				u64 *res)
+{
+	u64 clk_rate = READ_ONCE(st->clk.rate);
+	char *orig_str, *modf_str, *int_part, frac_part[7];
+	long ival, frac;
+	int ret;
+
+	orig_str = kstrdup(buf, GFP_KERNEL);
+	int_part = strsep(&orig_str, ".");
+	ret = kstrtol(int_part, 10, &ival);
+	if (ret || ival < 0)
+		return -EINVAL;
+	modf_str = strsep(&orig_str, ".");
+	if (modf_str) {
+		snprintf(frac_part, 7, "%s00000", modf_str);
+		ret = kstrtol(frac_part, 10, &frac);
+		if (ret)
+			return -EINVAL;
+	} else {
+		frac = 0;
+	}
+
+	*res = DIV_ROUND_CLOSEST_ULL((u64)ival * clk_rate, 1000)
+		+ DIV_ROUND_CLOSEST_ULL((u64)frac * clk_rate, 1000000000);
+
+	kfree(orig_str);
+
+	return ret;
+}
+
+static int adi_axi_tdd_write_regs(const struct adi_axi_tdd_attribute *attr,
+				  struct adi_axi_tdd_state *st,
+				  const char *buf)
+{
+	u32 channel = attr->channel;
+	u64 data64;
+	u32 data;
+	int ret;
+
+	switch (attr->id) {
+	case ADI_TDD_ATTR_SCRATCH:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs, ADI_REG_TDD_SCRATCH, data);
+	case ADI_TDD_ATTR_SYNC_SOFT:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs, ADI_REG_TDD_CONTROL,
+					       ADI_TDD_SYNC_SOFT,
+					       ADI_TDD_SYNC_SOFT * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_SYNC_EXT:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs, ADI_REG_TDD_CONTROL,
+					       ADI_TDD_SYNC_EXT,
+					       ADI_TDD_SYNC_EXT * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_SYNC_INT:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs, ADI_REG_TDD_CONTROL,
+					       ADI_TDD_SYNC_INT,
+					       ADI_TDD_SYNC_INT * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_SYNC_RST:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs, ADI_REG_TDD_CONTROL,
+					       ADI_TDD_SYNC_RST,
+					       ADI_TDD_SYNC_RST * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_ENABLE:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs, ADI_REG_TDD_CONTROL,
+					       ADI_TDD_ENABLE,
+					       ADI_TDD_ENABLE * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_BURST_COUNT:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs, ADI_REG_TDD_BURST_COUNT, data);
+	case ADI_TDD_ATTR_STARTUP_DELAY_RAW:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs, ADI_REG_TDD_STARTUP_DELAY, data);
+	case ADI_TDD_ATTR_STARTUP_DELAY_MS:
+		ret = adi_axi_tdd_parse_ms(st, buf, &data64);
+		if (ret)
+			return ret;
+		if (FIELD_GET(GENMASK_ULL(63, 32), data64))
+			return -EINVAL;
+		return regmap_write(st->regs, ADI_REG_TDD_STARTUP_DELAY,
+				    (u32)data64);
+	case ADI_TDD_ATTR_FRAME_LENGTH_RAW:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs, ADI_REG_TDD_FRAME_LENGTH, data);
+	case ADI_TDD_ATTR_FRAME_LENGTH_MS:
+		ret = adi_axi_tdd_parse_ms(st, buf, &data64);
+		if (ret)
+			return ret;
+		if (FIELD_GET(GENMASK_ULL(63, 32), data64))
+			return -EINVAL;
+		return regmap_write(st->regs, ADI_REG_TDD_FRAME_LENGTH,
+				    (u32)data64);
+	case ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_RAW:
+		ret = kstrtou64(buf, 0, &data64);
+		if (ret)
+			return ret;
+		return regmap_bulk_write(st->regs, ADI_REG_TDD_SYNC_COUNTER_LOW,
+					 &data64, 2);
+	case ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_MS:
+		ret = adi_axi_tdd_parse_ms(st, buf, &data64);
+		if (ret)
+			return ret;
+		return regmap_bulk_write(st->regs, ADI_REG_TDD_SYNC_COUNTER_LOW,
+					 &data64, 2);
+	case ADI_TDD_ATTR_CHANNEL_ENABLE:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs,
+					       ADI_REG_TDD_CHANNEL_ENABLE,
+					       BIT(channel),
+					       BIT(channel) * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_CHANNEL_POLARITY:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_update_bits_base(st->regs,
+					       ADI_REG_TDD_CHANNEL_POLARITY,
+					       BIT(channel),
+					       BIT(channel) * !!data,
+					       NULL, false, false);
+	case ADI_TDD_ATTR_CHANNEL_ON_RAW:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs,
+				    ADI_REG_TDD_CHANNEL(channel,
+							ADI_TDD_CHANNEL_ON),
+				    data);
+	case ADI_TDD_ATTR_CHANNEL_ON_MS:
+		ret = adi_axi_tdd_parse_ms(st, buf, &data64);
+		if (ret)
+			return ret;
+		if (FIELD_GET(GENMASK_ULL(63, 32), data64))
+			return -EINVAL;
+		return regmap_write(st->regs,
+				    ADI_REG_TDD_CHANNEL(channel,
+							ADI_TDD_CHANNEL_ON),
+				    (u32)data64);
+	case ADI_TDD_ATTR_CHANNEL_OFF_RAW:
+		ret = kstrtou32(buf, 0, &data);
+		if (ret)
+			return ret;
+		return regmap_write(st->regs,
+				    ADI_REG_TDD_CHANNEL(channel,
+							ADI_TDD_CHANNEL_OFF),
+				    data);
+	case ADI_TDD_ATTR_CHANNEL_OFF_MS:
+		ret = adi_axi_tdd_parse_ms(st, buf, &data64);
+		if (ret)
+			return ret;
+		if (FIELD_GET(GENMASK_ULL(63, 32), data64))
+			return -EINVAL;
+		return regmap_write(st->regs,
+				    ADI_REG_TDD_CHANNEL(channel,
+							ADI_TDD_CHANNEL_OFF),
+				    (u32)data64);
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t adi_axi_tdd_store(struct device *dev,
+				 struct device_attribute *dev_attr,
+				 const char *buf, size_t count)
+{
+	const struct adi_axi_tdd_attribute *attr = to_tdd_attribute(dev_attr);
+	struct adi_axi_tdd_state *st = dev_get_drvdata(dev);
+
+	return adi_axi_tdd_write_regs(attr, st, buf) ?: count;
+}
+
+static int adi_axi_tdd_init_synthesis_parameters(struct adi_axi_tdd_state *st)
+{
+	u32 interface_config;
+	int ret;
+
+	ret = regmap_read(st->regs, ADI_REG_TDD_INTERFACE_DESCRIPTION,
+			  &interface_config);
+	if (ret)
+		return ret;
+
+	st->sync_count_width = FIELD_GET(ADI_TDD_SYNC_COUNT_WIDTH,
+					 interface_config);
+	st->burst_count_width = FIELD_GET(ADI_TDD_BURST_COUNT_WIDTH,
+					  interface_config);
+	st->reg_width = FIELD_GET(ADI_TDD_REG_WIDTH, interface_config);
+	st->sync_external_cdc = ADI_TDD_SYNC_EXTERNAL_CDC & interface_config;
+	st->sync_external = ADI_TDD_SYNC_EXTERNAL & interface_config;
+	st->sync_internal = ADI_TDD_SYNC_INTERNAL & interface_config;
+	st->channel_count = FIELD_GET(ADI_TDD_CHANNEL_COUNT,
+				      interface_config) + 1;
+
+	if (!st->burst_count_width || !st->reg_width)
+		return -EINVAL;
+
+	st->sync_count_mask = GENMASK_ULL(st->sync_count_width - 1, 0);
+	st->burst_count_mask = GENMASK_ULL(st->burst_count_width - 1, 0);
+	st->reg_mask = GENMASK_ULL(st->reg_width - 1, 0);
+
+	return ret;
+}
+
+#define __TDD_ATTR(_name, _id, _channel, _mode)                         \
+	{								\
+		.attr = __ATTR(_name, _mode, adi_axi_tdd_show,          \
+				adi_axi_tdd_store),                     \
+		.id = _id,                                              \
+		.channel = _channel                                     \
+	}
+
+#define TDD_ATTR(_name, _id, _mode)                                     \
+	struct adi_axi_tdd_attribute dev_attr_##_name =                 \
+		__TDD_ATTR(_name, _id, 0, _mode)                        \
+
+static const TDD_ATTR(version, ADI_TDD_ATTR_VERSION, 0444);
+static const TDD_ATTR(core_id, ADI_TDD_ATTR_CORE_ID, 0444);
+static const TDD_ATTR(scratch, ADI_TDD_ATTR_SCRATCH, 0644);
+static const TDD_ATTR(magic, ADI_TDD_ATTR_MAGIC, 0444);
+
+static const TDD_ATTR(sync_soft, ADI_TDD_ATTR_SYNC_SOFT, 0200);
+static const TDD_ATTR(sync_external, ADI_TDD_ATTR_SYNC_EXT, 0644);
+static const TDD_ATTR(sync_internal, ADI_TDD_ATTR_SYNC_INT, 0644);
+static const TDD_ATTR(sync_reset, ADI_TDD_ATTR_SYNC_RST, 0644);
+static const TDD_ATTR(enable, ADI_TDD_ATTR_ENABLE, 0644);
+
+static const TDD_ATTR(burst_count, ADI_TDD_ATTR_BURST_COUNT, 0644);
+static const TDD_ATTR(startup_delay_raw, ADI_TDD_ATTR_STARTUP_DELAY_RAW, 0644);
+static const TDD_ATTR(startup_delay_ms, ADI_TDD_ATTR_STARTUP_DELAY_MS, 0644);
+static const TDD_ATTR(frame_length_raw, ADI_TDD_ATTR_FRAME_LENGTH_RAW, 0644);
+static const TDD_ATTR(frame_length_ms, ADI_TDD_ATTR_FRAME_LENGTH_MS, 0644);
+static const TDD_ATTR(internal_sync_period_raw,
+		      ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_RAW, 0644);
+static const TDD_ATTR(internal_sync_period_ms,
+		      ADI_TDD_ATTR_INTERNAL_SYNC_PERIOD_MS, 0644);
+
+static const TDD_ATTR(state, ADI_TDD_ATTR_STATE, 0444);
+
+static const struct attribute *adi_axi_tdd_base_attributes[] = {
+	&dev_attr_version.attr.attr,
+	&dev_attr_core_id.attr.attr,
+	&dev_attr_scratch.attr.attr,
+	&dev_attr_magic.attr.attr,
+	&dev_attr_sync_soft.attr.attr,
+	&dev_attr_sync_external.attr.attr,
+	&dev_attr_sync_internal.attr.attr,
+	&dev_attr_sync_reset.attr.attr,
+	&dev_attr_enable.attr.attr,
+	&dev_attr_burst_count.attr.attr,
+	&dev_attr_startup_delay_raw.attr.attr,
+	&dev_attr_startup_delay_ms.attr.attr,
+	&dev_attr_frame_length_raw.attr.attr,
+	&dev_attr_frame_length_ms.attr.attr,
+	&dev_attr_internal_sync_period_raw.attr.attr,
+	&dev_attr_internal_sync_period_ms.attr.attr,
+	&dev_attr_state.attr.attr,
+	/* NOT TERMINATED */
+};
+
+static const char * const channel_names[] = {
+	"out_channel%u_enable", "out_channel%u_polarity",
+	"out_channel%u_on_raw", "out_channel%u_off_raw",
+	"out_channel%u_on_ms", "out_channel%u_off_ms"
+};
+
+static const enum adi_axi_tdd_attribute_id channel_ids[] = {
+	ADI_TDD_ATTR_CHANNEL_ENABLE, ADI_TDD_ATTR_CHANNEL_POLARITY,
+	ADI_TDD_ATTR_CHANNEL_ON_RAW, ADI_TDD_ATTR_CHANNEL_OFF_RAW,
+	ADI_TDD_ATTR_CHANNEL_ON_MS, ADI_TDD_ATTR_CHANNEL_OFF_MS
+};
+
+static int adi_axi_tdd_init_sysfs(struct platform_device *pdev,
+				  struct adi_axi_tdd_state *st)
+{
+	size_t base_attr_count = ARRAY_SIZE(adi_axi_tdd_base_attributes);
+	size_t attribute_count = base_attr_count + 6 * st->channel_count + 1;
+	struct adi_axi_tdd_attribute *channel_attributes;
+	struct adi_axi_tdd_attribute *channel_iter;
+	struct attribute_group *attr_group;
+	struct attribute **tdd_attrs;
+	u32 i, j;
+
+	channel_attributes = devm_kcalloc(&pdev->dev, 6 * st->channel_count,
+					  sizeof(*channel_attributes),
+					  GFP_KERNEL);
+	if (!channel_attributes)
+		return -ENOMEM;
+
+	tdd_attrs = devm_kcalloc(&pdev->dev, attribute_count,
+				 sizeof(*tdd_attrs), GFP_KERNEL);
+	if (!tdd_attrs)
+		return -ENOMEM;
+
+	memcpy(tdd_attrs, adi_axi_tdd_base_attributes,
+	       sizeof(adi_axi_tdd_base_attributes));
+
+	channel_iter = channel_attributes;
+
+	for (i = 0; i < st->channel_count; i++) {
+		for (j = 0; j < ARRAY_SIZE(channel_names); j++) {
+			snprintf(channel_iter->name,
+				 sizeof(channel_iter->name), channel_names[j],
+				 i);
+			channel_iter->id = channel_ids[j];
+			channel_iter->channel = i;
+			channel_iter->attr.attr.name = channel_iter->name;
+			channel_iter->attr.attr.mode = 0644;
+			channel_iter->attr.show = adi_axi_tdd_show;
+			channel_iter->attr.store = adi_axi_tdd_store;
+
+			tdd_attrs[base_attr_count + 6 * i + j] =
+				&channel_iter->attr.attr;
+			channel_iter++;
+		}
+	}
+
+	attr_group = devm_kzalloc(&pdev->dev, sizeof(attr_group), GFP_KERNEL);
+	if (!attr_group)
+		return -ENOMEM;
+
+	attr_group->attrs = tdd_attrs;
+
+	return devm_device_add_group(&pdev->dev, attr_group);
+}
+
+static int adi_axi_tdd_rate_change(struct notifier_block *nb,
+				   unsigned long flags, void *data)
+{
+	struct adi_axi_tdd_clk *clk =
+		container_of(nb, struct adi_axi_tdd_clk, nb);
+	struct clk_notifier_data *cnd = data;
+
+	/* cache the new rate */
+	WRITE_ONCE(clk->rate, cnd->new_rate);
+
+	return NOTIFY_OK;
+}
+
+static void adi_axi_tdd_clk_notifier_unreg(void *data)
+{
+	struct adi_axi_tdd_clk *clk = data;
+
+	clk_notifier_unregister(clk->clk, &clk->nb);
+}
+
+static int adi_axi_tdd_probe(struct platform_device *pdev)
+{
+	unsigned int expected_version, version, data;
+	struct adi_axi_tdd_state *st;
+	struct clk *aclk;
+	int ret;
+
+	st = devm_kzalloc(&pdev->dev, sizeof(*st), GFP_KERNEL);
+	if (!st)
+		return -ENOMEM;
+
+	st->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(st->base))
+		return PTR_ERR(st->base);
+
+	platform_set_drvdata(pdev, st);
+
+	aclk = devm_clk_get_enabled(&pdev->dev, "s_axi_aclk");
+	if (IS_ERR(aclk))
+		return PTR_ERR(aclk);
+
+	st->clk.clk = devm_clk_get_enabled(&pdev->dev, "intf_clk");
+	if (IS_ERR(st->clk.clk))
+		return PTR_ERR(st->clk.clk);
+
+	st->clk.rate = clk_get_rate(st->clk.clk);
+	st->clk.nb.notifier_call = adi_axi_tdd_rate_change;
+	ret = clk_notifier_register(st->clk.clk, &st->clk.nb);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(&pdev->dev,
+				       adi_axi_tdd_clk_notifier_unreg,
+				       st->clk.clk);
+	if (ret)
+		return ret;
+
+	st->regs = devm_regmap_init_mmio(&pdev->dev, st->base,
+					 &adi_axi_tdd_regmap_cfg);
+	if (IS_ERR(st->regs))
+		return PTR_ERR(st->regs);
+
+	ret = regmap_read(st->regs, ADI_AXI_REG_VERSION, &version);
+	if (ret)
+		return ret;
+
+	expected_version = ADI_AXI_PCORE_VER(2, 0, 'a');
+
+	if (ADI_AXI_PCORE_VER_MAJOR(version) !=
+	    ADI_AXI_PCORE_VER_MAJOR(expected_version))
+		return dev_err_probe(&pdev->dev,
+				     -ENODEV,
+				     "Major version mismatch between PCORE and driver. Driver expected %d.%.2d.%c, PCORE reported %d.%.2d.%c\n",
+				     ADI_AXI_PCORE_VER_MAJOR(expected_version),
+				     ADI_AXI_PCORE_VER_MINOR(expected_version),
+				     ADI_AXI_PCORE_VER_PATCH(expected_version),
+				     ADI_AXI_PCORE_VER_MAJOR(version),
+				     ADI_AXI_PCORE_VER_MINOR(version),
+				     ADI_AXI_PCORE_VER_PATCH(version));
+
+	ret = adi_axi_tdd_init_synthesis_parameters(st);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret,
+				     "Failed to load synthesis parameters, make sure the device is configured correctly.\n");
+
+	ret = regmap_read(st->regs, ADI_REG_TDD_CONTROL, &data);
+	if (ret)
+		return ret;
+
+	st->enabled =  data & ADI_TDD_ENABLE;
+
+	ret = adi_axi_tdd_init_sysfs(pdev, st);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to init sysfs, aborting ...\n");
+
+	dev_dbg(&pdev->dev, "Probed Analog Devices AXI TDD (%d.%.2d.%c)",
+		ADI_AXI_PCORE_VER_MAJOR(version),
+		ADI_AXI_PCORE_VER_MINOR(version),
+		ADI_AXI_PCORE_VER_PATCH(version));
+
+	return 0;
+}
+
+/* Match table for of_platform binding */
+static const struct of_device_id adi_axi_tdd_of_match[] = {
+	{ .compatible = "adi,axi-tdd" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adi_axi_tdd_of_match);
+
+static struct platform_driver adi_axi_tdd_driver = {
+	.driver = {
+		.name = "adi-axi-tdd",
+		.of_match_table = adi_axi_tdd_of_match,
+	},
+	.probe = adi_axi_tdd_probe,
+};
+module_platform_driver(adi_axi_tdd_driver);
+
+MODULE_AUTHOR("Eliza Balas <eliza.balas@xxxxxxxxxx>");
+MODULE_AUTHOR("David Winter <david.winter@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Analog Devices TDD HDL CORE driver");
+MODULE_LICENSE("GPL");
-- 
2.25.1





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux