[PATCH 2/2] misc: Add initial Digital Timing Engine (DTE) driver for cygnus

Jonathan Richardson jonathar at broadcom.com
Wed Apr 22 16:22:03 PDT 2015


Reviewed-by: Scott Branden <sbranden at broadcom.com>
Tested-by: Scott Branden <sbranden at broadcom.com>
Signed-off-by: Jonathan Richardson <jonathar at broadcom.com>
---
 drivers/misc/Kconfig           |   11 +
 drivers/misc/Makefile          |    1 +
 drivers/misc/bcm_cygnus_dte.c  |  927 ++++++++++++++++++++++++++++++++++++++++
 include/linux/bcm_cygnus_dte.h |   99 +++++
 4 files changed, 1038 insertions(+)
 create mode 100644 drivers/misc/bcm_cygnus_dte.c
 create mode 100644 include/linux/bcm_cygnus_dte.h

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 006242c..2a223c3 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -515,6 +515,17 @@ config VEXPRESS_SYSCFG
 	  bus. System Configuration interface is one of the possible means
 	  of generating transactions on this bus.
 
+config BCM_CYGNUS_DTE
+	tristate "Cygnus DTE support"
+	depends on ARCH_BCM_CYGNUS
+	default ARCH_BCM_CYGNUS
+	help
+	  Enable support for a Digital Timing Engine (DTE) that allows for hardware
+	  time stamping of network packets, audio/video samples and/or GPIO inputs
+	  in a common adjustable time base.
+
+	  If unsure, say N.
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 7d5c4cd..4067b95 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE)		+= genwqe/
 obj-$(CONFIG_ECHO)		+= echo/
 obj-$(CONFIG_VEXPRESS_SYSCFG)	+= vexpress-syscfg.o
 obj-$(CONFIG_CXL_BASE)		+= cxl/
+obj-$(CONFIG_BCM_CYGNUS_DTE)	+= bcm_cygnus_dte.o
diff --git a/drivers/misc/bcm_cygnus_dte.c b/drivers/misc/bcm_cygnus_dte.c
new file mode 100644
index 0000000..8bf4f93
--- /dev/null
+++ b/drivers/misc/bcm_cygnus_dte.c
@@ -0,0 +1,927 @@
+/*
+ * Copyright 2015 Broadcom Corporation.  All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License as
+ * published by the Free Software Foundation version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * This driver implements the Broadcom DTE Digital Timing Engine Driver (DTE).
+ * The DTE allows for hardware time stamping of network packets, audio/video
+ * samples and/or GPIO inputs in a common adjustable time base.
+ */
+
+#include <linux/hw_random.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/printk.h>
+#include <linux/delay.h>
+#include <linux/kfifo.h>
+#include <linux/interrupt.h>
+#include <linux/uaccess.h>
+#include <linux/bcm_cygnus_dte.h>
+
+#define DTE_SW_FIFO_SIZE 64
+#define DTE_HW_FIFO_SIZE 16
+#define DTE_MAX_DEVS 1
+#define DTE_DEVICE_NAME	"dte"
+
+/* Registers */
+#define DTE_CTRL_REG_BASE                        0x600
+#define DTE_CTRL_REG__INTERRUPT                  16
+#define DTE_NEXT_SOI_REG_BASE                    0x610
+#define DTE_ILEN_REG_BASE                        0x614
+#define DTE_LTS_FIFO_REG_BASE                    0x640
+#define DTE_LTS_CSR_REG_BASE                     0x644
+#define DTE_LTS_CSR_REG__FIFO_EMPTY              4
+#define DTE_LTS_CSR_REG__FIFO_OVERFLOW           3
+#define DTE_LTS_CSR_REG__FIFO_UNDERFLOW          2
+#define DTE_NCO_LOW_TIME_REG_BASE                0x650
+#define DTE_NCO_TIME_REG_BASE                    0x654
+#define DTE_NCO_OVERFLOW_REG_BASE                0x658
+#define DTE_NCO_INC_REG_BASE                     0x65c
+#define DTE_LTS_DIV_54_REG_BASE                  0x660
+#define DTE_LTS_DIV_76_REG_BASE                  0x664
+#define DTE_LTS_DIV_98_REG_BASE                  0x668
+#define DTE_LTS_DIV_1110_REG_BASE                0x66c
+#define DTE_LTS_DIV_1312_REG_BASE                0x670
+#define DTE_LTS_DIV_14_REG_BASE                  0x674
+#define DTE_LTS_DIV_MASK                         0xffff
+#define DTE_LTS_DIV_54_REG__DIV_VAL_4_R          0
+#define DTE_LTS_DIV_54_REG__DIV_VAL_5_R          16
+#define DTE_LTS_DIV_76_REG__DIV_VAL_6_R          0
+#define DTE_LTS_DIV_76_REG__DIV_VAL_7_R          16
+#define DTE_LTS_DIV_98_REG__DIV_VAL_8_R          0
+#define DTE_LTS_DIV_98_REG__DIV_VAL_9_R          16
+#define DTE_LTS_DIV_1110_REG__DIV_VAL_10_R       0
+#define DTE_LTS_DIV_1110_REG__DIV_VAL_11_R       16
+#define DTE_LTS_DIV_1312_REG__DIV_VAL_12_R       0
+#define DTE_LTS_DIV_1312_REG__DIV_VAL_13_R       16
+#define DTE_LTS_DIV_14_REG__DIV_VAL_15_R         0
+#define DTE_LTS_DIV_14_REG__DIV_VAL_14_R         16
+#define DTE_LTS_SRC_EN_REG_BASE                  0x680
+#define DTE_INTR_STATUS_REG                      0x6A0
+#define DTE_INTR_STATUS_ISO_INTR_SHIFT           0
+#define DTE_INTR_STATUS_ISO_INTR_MASK            0x1
+#define DTE_INTR_STATUS_FIFO_LEVEL_INTR_SHIFT    1
+#define DTE_INTR_STATUS_FIFO_LEVEL_INTR_MASK     0x7
+#define DTE_INTR_STATUS_TIMEOUT_INTR_SHIFT       4
+#define DTE_INTR_STATUS_TIMEOUT_INTR_MASK        0x1
+#define DTE_INTR_MASK_REG                        0x6A4
+#define ISO_INTR_MASK_SHIFT                      0
+
+/*
+ * There are 3 time registers in the DTE block;
+ * NCO_OVERFLOW[7:0] (sum3)
+ * NCO_TIME[31:0] (sum2)
+ * NCO_LOW_TIME[31:0] (sum1).
+ * These can be considered as a 72 bit overall timestamp[71:0]
+ * in ns with a format of 44.28
+ *
+ * The DTE block runs at 125MHz, ie; every 8ns,
+ * NCO_INC is added to the timestamp[71:0].
+ * NCO_INC represents fractional ns in 4.28 format.
+ *
+ * The DTE client Timstamps have a resoultion of ns and is constructed
+ * from the 28 LS bits of sum2 and 4 MS bits of sum1.
+ *
+ * Register            |     sum3    |          sum2     |         sum1       |
+ *                     |7           0|31  28            0|31    28:27        0|
+ * Timestamp[71:0]     |71                                      28:27        0|
+ * NCO_INC[31:0]                                         |31    28:27        0|
+ * DTE client TS[31:0]                      |31                  0|
+ */
+
+/* NCO nominal increment = 8ns DTE operates at 125 MHz */
+#define NCO_INC_NOMINAL 0x80000000
+
+struct bcm_cygnus_dte {
+	struct list_head node;
+	struct platform_device *pdev;
+	struct cdev dte_cdev;
+	struct class *dte_class;
+	struct kfifo recv_fifo[DTE_CLIENT_MAX];
+	dev_t  devt;
+	uint32_t fifoof;
+	uint32_t fifouf;
+	uint32_t kfifoof[DTE_CLIENT_MAX];
+	spinlock_t lock;
+	struct mutex mutex;
+	struct timespec ts_ref;           /* Reference base timespec */
+	uint32_t timestamp_overflow_last; /* Last timestamp overflow */
+	void __iomem *audioeav;
+};
+
+static LIST_HEAD(dtedev_list);
+
+struct dte_client_mapping {
+	enum dte_client client_index;
+	int lts_index;
+	uint32_t reg_offset;
+	int shift;
+};
+
+#define DTE_CLIENT_MAPPING_ENTRY(client, lts_index, regbase) \
+	{ DTE_CLIENT_##client, \
+	  lts_index,\
+	  DTE_LTS_##regbase##_BASE, \
+	  DTE_LTS_##regbase##__DIV_VAL_##lts_index##_R }
+
+static const struct dte_client_mapping dte_client_map_table[DTE_CLIENT_MAX] = {
+	DTE_CLIENT_MAPPING_ENTRY(I2S0_BITCLOCK,  4, DIV_54_REG),
+	DTE_CLIENT_MAPPING_ENTRY(I2S1_BITCLOCK,  5, DIV_54_REG),
+	DTE_CLIENT_MAPPING_ENTRY(I2S2_BITCLOCK,  6, DIV_76_REG),
+	DTE_CLIENT_MAPPING_ENTRY(I2S0_WORDCLOCK, 7, DIV_76_REG),
+	DTE_CLIENT_MAPPING_ENTRY(I2S1_WORDCLOCK, 8, DIV_98_REG),
+	DTE_CLIENT_MAPPING_ENTRY(I2S2_WORDCLOCK, 9, DIV_98_REG),
+	DTE_CLIENT_MAPPING_ENTRY(LCD_CLFP,      10, DIV_1110_REG),
+	DTE_CLIENT_MAPPING_ENTRY(LCD_CLLP,      11, DIV_1110_REG),
+	DTE_CLIENT_MAPPING_ENTRY(GPIO14,        12, DIV_1312_REG),
+	DTE_CLIENT_MAPPING_ENTRY(GPIO15,        13, DIV_1312_REG),
+	DTE_CLIENT_MAPPING_ENTRY(GPIO22,        14, DIV_14_REG),
+	DTE_CLIENT_MAPPING_ENTRY(GPIO23,        15, DIV_14_REG)
+};
+
+struct bcm_cygnus_dte *dte_get_dev_from_devname(const char *devname)
+{
+	struct bcm_cygnus_dte *cygnus_dte = NULL;
+	bool found = false;
+
+	if (!devname)
+		return NULL;
+
+	list_for_each_entry(cygnus_dte, &dtedev_list, node) {
+		if (!strcmp(dev_name(&cygnus_dte->pdev->dev), devname)) {
+			/* Matched on device name */
+			found = true;
+			break;
+		}
+	}
+
+	return found ? cygnus_dte : NULL;
+}
+EXPORT_SYMBOL(dte_get_dev_from_devname);
+
+int dte_enable_timestamp(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		int enable)
+{
+	int src_ena;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	if (client < DTE_CLIENT_MIN || client >= DTE_CLIENT_MAX)
+		return -EINVAL;
+
+	spin_lock(&cygnus_dte->lock);
+
+	src_ena = readl(cygnus_dte->audioeav +
+		DTE_LTS_SRC_EN_REG_BASE);
+	if (enable)
+		src_ena |= BIT(dte_client_map_table[client].lts_index);
+	else
+		src_ena &= ~BIT(dte_client_map_table[client].lts_index);
+
+	writel(src_ena,
+		cygnus_dte->audioeav + DTE_LTS_SRC_EN_REG_BASE);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_enable_timestamp);
+
+int dte_set_client_divider(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		int divider)
+{
+	int lts_div;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	if (client < DTE_CLIENT_MIN || client >= DTE_CLIENT_MAX)
+		return -EINVAL;
+
+	/* Divider not supported for Divider 15 (GPIO23) */
+	if (client == DTE_CLIENT_GPIO23)
+		return -EINVAL;
+
+	/* Check for maximum divider size */
+	if (divider > DTE_LTS_DIV_MASK)
+		return -EINVAL;
+
+	spin_lock(&cygnus_dte->lock);
+
+	lts_div = readl(cygnus_dte->audioeav +
+			dte_client_map_table[client].reg_offset);
+	lts_div &= ~(DTE_LTS_DIV_MASK << dte_client_map_table[client].shift);
+	lts_div |= (divider << dte_client_map_table[client].shift);
+	writel(lts_div, cygnus_dte->audioeav +
+		dte_client_map_table[client].reg_offset);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_set_client_divider);
+
+int dte_set_irq_interval(
+		struct bcm_cygnus_dte *cygnus_dte,
+		uint32_t nanosec)
+{
+	int next_soi;
+	int current_time;
+	int intr_mask;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	spin_lock(&cygnus_dte->lock);
+
+	/*
+	 * To ensure proper operation of the isochronous time interval
+	 * generation (ITG) timing pulse requires programming of
+	 * 1) Next Start of Interrupt Time (ns) (NEXT_SOI)
+	 * 2) Interval Length (ns) (ILEN)
+	 * The block first compares the value of the NEXT_SOI with
+	 * that of the 29-bit value of time DTE_NCO_TIME_REG_BASE
+	 * that has been left-shifted by 4 or multiplied by 16 for
+	 * generating the first interrupt.  Thereafter upon completion
+	 * of every ILEN number of nano-seconds it generates an ITG
+	 * interrupt and the NEXT_SOI register is auto-incremented by ILEN
+	 */
+
+	/* Get the current time (sum2) (units of 16ns) */
+	current_time = readl(cygnus_dte->audioeav + DTE_NCO_TIME_REG_BASE);
+
+	/*
+	 * Set the Start of Next Interval (units of ns) to trigger on next
+	 * interval
+	 */
+	next_soi = (current_time << 4) + (nanosec);
+	writel(next_soi, cygnus_dte->audioeav + DTE_NEXT_SOI_REG_BASE);
+
+	/* configure interval length (units of ns) */
+	writel(nanosec, cygnus_dte->audioeav + DTE_ILEN_REG_BASE);
+
+	/* enable isochronous interrupt */
+	intr_mask = readl(cygnus_dte->audioeav + DTE_INTR_MASK_REG);
+	intr_mask &= ~(1 << ISO_INTR_MASK_SHIFT);
+	writel(intr_mask, cygnus_dte->audioeav + DTE_INTR_MASK_REG);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_set_irq_interval);
+
+int dte_get_timestamp(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		struct timespec *ts)
+{
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	if (client < DTE_CLIENT_MIN || client >= DTE_CLIENT_MAX)
+		return -EINVAL;
+
+	if (kfifo_out(
+		&cygnus_dte->recv_fifo[client],
+		ts, sizeof(struct timespec)) == 0)
+		return -EPERM;
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_get_timestamp);
+
+int dte_set_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		struct timespec *ts)
+{
+	int nco_incr;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	spin_lock(&cygnus_dte->lock);
+
+	/* Disable NCO Increment */
+	nco_incr = readl(cygnus_dte->audioeav + DTE_NCO_INC_REG_BASE);
+	writel(0, cygnus_dte->audioeav + DTE_NCO_INC_REG_BASE);
+
+	/* Reset Timers */
+	writel(0, cygnus_dte->audioeav + DTE_NCO_LOW_TIME_REG_BASE);
+	writel(0, cygnus_dte->audioeav + DTE_NCO_TIME_REG_BASE);
+	writel(0, cygnus_dte->audioeav + DTE_NCO_OVERFLOW_REG_BASE);
+
+	/* Initialize last overflow value to track wrap condition */
+	cygnus_dte->timestamp_overflow_last = 0;
+
+	/* Initialize Reference timespec */
+	cygnus_dte->ts_ref = *ts;
+
+	/* re-enable nco increment */
+	writel(nco_incr, cygnus_dte->audioeav + DTE_NCO_INC_REG_BASE);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_set_time);
+
+int dte_get_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		struct timespec *ts)
+{
+	uint32_t current_time_sum1;
+	uint32_t current_time_sum2;
+	uint32_t current_time_sum3;
+	uint32_t timestamp_overflow;
+	uint64_t ns;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	spin_lock(&cygnus_dte->lock);
+
+	/* Read Timers */
+	current_time_sum1 = readl(cygnus_dte->audioeav +
+				DTE_NCO_LOW_TIME_REG_BASE);
+	current_time_sum2 = readl(cygnus_dte->audioeav +
+				DTE_NCO_TIME_REG_BASE);
+	current_time_sum3 = readl(cygnus_dte->audioeav +
+				DTE_NCO_OVERFLOW_REG_BASE);
+/*
+ * Register            |     sum3    |          sum2     |         sum1       |
+ *                     |7           0|31  28            0|31    28:27        0|
+ * Timestamp[71:0]     |71                                      28:27        0|
+ */
+
+	/* Current time in units of ns */
+	ns = (((uint64_t)(current_time_sum3 & 0xff) << 36) |
+		((uint64_t)current_time_sum2 << 4) |
+		(uint64_t)((current_time_sum1 >> 28) & 0xf));
+
+	/*
+	 * Determined if wraparound has occurred
+	 * Timestamp overflow only includes the bottom 8 bits of sum3
+	 * and the top 4 bits of sum2
+	 * Units of 2^32 ns = 4.294967296 sec
+	 */
+	timestamp_overflow = (ns >> 32) & 0xfff;
+	if (timestamp_overflow < cygnus_dte->timestamp_overflow_last)
+		/*
+		 * Wrap around has occurred but not yet reflected in
+		 * the reference timespec
+		 * Full wrap around amount is 44bits in ns
+		 * Precisely 17592.186044416 sec = ~4.887 hrs
+		 */
+		ns += (uint64_t)0x1<<44;
+
+	/* Convert current timestamp(ns) to timespec */
+	*ts = ns_to_timespec(ns);
+
+	/* Add the current time to the reference time */
+	*ts = timespec_add(cygnus_dte->ts_ref, *ts);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_get_time);
+
+int dte_adj_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		int64_t delta)
+{
+	struct timespec ts_delta;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	ts_delta = ns_to_timespec(delta);
+
+	spin_lock(&cygnus_dte->lock);
+
+	/* Update Reference timespec */
+	cygnus_dte->ts_ref = timespec_add(cygnus_dte->ts_ref, ts_delta);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_adj_time);
+
+int dte_adj_freq(
+		struct bcm_cygnus_dte *cygnus_dte,
+		int32_t ppb)
+{
+	uint64_t diff;
+	uint32_t mult = NCO_INC_NOMINAL;
+	uint32_t nco_incr;
+	int neg_adj = 0;
+
+	if (!cygnus_dte)
+		return -EINVAL;
+
+	if (ppb < 0) {
+		ppb = -ppb;
+		neg_adj = 1;
+	}
+
+	diff = mult;
+	diff *= ppb;
+	diff = div_u64(diff, 1000000000ULL);
+
+	nco_incr = neg_adj ? mult - diff : mult + diff;
+
+	spin_lock(&cygnus_dte->lock);
+
+	/* Update NCO Increment */
+	writel(nco_incr, cygnus_dte->audioeav + DTE_NCO_INC_REG_BASE);
+
+	spin_unlock(&cygnus_dte->lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(dte_adj_freq);
+
+static int dte_open(struct inode *pnode, struct file *fp)
+{
+	/* look up device info for this device file */
+	struct bcm_cygnus_dte *cygnus_dte = container_of(pnode->i_cdev,
+			struct bcm_cygnus_dte, dte_cdev);
+
+	fp->private_data = cygnus_dte;
+
+	return 0;
+}
+
+static long dte_ioctl(struct file *filep,
+	unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+	struct dte_data dte_data;
+	struct dte_timestamp dte_timestamp;
+	struct timespec ts;
+	int64_t delta;
+	int32_t ppb;
+	struct bcm_cygnus_dte *cygnus_dte = filep->private_data;
+
+	mutex_lock(&cygnus_dte->mutex);
+	switch (cmd) {
+	case DTE_IOCTL_SET_DIVIDER:
+		if (copy_from_user(&dte_data, (struct dte_data *)arg,
+				sizeof(struct dte_data))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_set_client_divider(cygnus_dte,
+				dte_data.client, dte_data.data))
+			ret = -EPERM;
+		break;
+
+	case DTE_IOCTL_ENABLE_TIMESTAMP:
+		if (copy_from_user(&dte_data, (struct dte_data *)arg,
+				sizeof(struct dte_data))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_enable_timestamp(cygnus_dte,
+				dte_data.client, dte_data.data))
+			ret = -EPERM;
+		break;
+
+	case DTE_IOCTL_SET_IRQ_INTERVAL:
+		if (copy_from_user(&dte_data, (struct dte_data *)arg,
+				sizeof(struct dte_data))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_set_irq_interval(cygnus_dte, dte_data.data))
+			ret = -EPERM;
+		break;
+
+	case DTE_IOCTL_GET_TIMESTAMP:
+		if (copy_from_user(&dte_timestamp, (struct dte_timestamp *)arg,
+				sizeof(struct dte_timestamp))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_get_timestamp(cygnus_dte,
+				dte_timestamp.client,
+				&dte_timestamp.ts)) {
+			ret = -EPERM;
+			goto err_out;
+		}
+		if (copy_to_user((struct dte_timestamp *)arg, &dte_timestamp,
+				sizeof(struct dte_timestamp))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		break;
+
+	case DTE_IOCTL_SET_TIME:
+		if (copy_from_user(&ts, (struct timespec *)arg,
+				sizeof(struct timespec))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_set_time(cygnus_dte, &ts))
+			ret = -EPERM;
+		break;
+
+	case DTE_IOCTL_GET_TIME:
+		if (dte_get_time(cygnus_dte, &ts))
+			ret = -EPERM;
+		if (copy_to_user((struct timespec *)arg, &ts,
+				sizeof(struct timespec))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		break;
+
+	case DTE_IOCTL_ADJ_TIME:
+		if (copy_from_user(&delta, (int64_t *)arg,
+				sizeof(int64_t))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_adj_time(cygnus_dte, delta))
+			ret = -EPERM;
+		break;
+
+	case DTE_IOCTL_ADJ_FREQ:
+		if (copy_from_user(&ppb, (int32_t *)arg,
+				sizeof(int32_t))) {
+			ret = -EACCES;
+			goto err_out;
+		}
+		if (dte_adj_freq(cygnus_dte, ppb))
+			ret = -EPERM;
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto err_out;
+	}
+
+err_out:
+	mutex_unlock(&cygnus_dte->mutex);
+	return ret;
+}
+
+static const struct file_operations dte_cdev_fops = {
+	.open           = dte_open,
+	.unlocked_ioctl = dte_ioctl,
+};
+
+static irqreturn_t bcm_cygnus_dte_isr_threaded(int irq, void *drv_ctx)
+{
+	uint32_t fifo_csr;
+	uint32_t rd_data;
+	uint32_t current_time_sum2;
+	uint32_t current_time_sum3;
+	uint32_t i = 0;
+	uint32_t active_clients;
+	int client;
+	int inlen;
+	struct platform_device *pdev = (struct platform_device *)drv_ctx;
+	struct bcm_cygnus_dte *cygnus_dte;
+	uint32_t timestamp_overflow;
+	uint32_t timestamp_ns;
+	uint64_t ns;
+	struct timespec ts_stamp;
+	uint32_t status;
+
+	cygnus_dte = (struct bcm_cygnus_dte *)platform_get_drvdata(pdev);
+
+	/* clear interrupt bit */
+	writel(1<<DTE_CTRL_REG__INTERRUPT,
+		cygnus_dte->audioeav + DTE_CTRL_REG_BASE);
+
+	/* clear all interrupts in status register */
+	status = (DTE_INTR_STATUS_ISO_INTR_MASK <<
+		DTE_INTR_STATUS_ISO_INTR_SHIFT) |
+		(DTE_INTR_STATUS_FIFO_LEVEL_INTR_MASK <<
+		DTE_INTR_STATUS_FIFO_LEVEL_INTR_SHIFT) |
+		(DTE_INTR_STATUS_TIMEOUT_INTR_MASK <<
+		DTE_INTR_STATUS_TIMEOUT_INTR_SHIFT);
+	writel(status, cygnus_dte->audioeav + DTE_INTR_STATUS_REG);
+
+	/* get data from dte fifo */
+	do {
+		fifo_csr = readl(cygnus_dte->audioeav +
+			DTE_LTS_CSR_REG_BASE);
+		/* fifo empty */
+		if (fifo_csr & BIT(DTE_LTS_CSR_REG__FIFO_EMPTY))
+			break;
+
+		/* overflow error */
+		if (fifo_csr & BIT(DTE_LTS_CSR_REG__FIFO_OVERFLOW))
+			break;
+
+		spin_lock(&cygnus_dte->lock);
+
+		/* first event contains active clients */
+		active_clients = readl(cygnus_dte->audioeav +
+			DTE_LTS_FIFO_REG_BASE);
+
+		/* then timestamp */
+		rd_data = readl(cygnus_dte->audioeav +
+			DTE_LTS_FIFO_REG_BASE);
+
+		/* Save the actual timestamp */
+		timestamp_ns = rd_data;
+
+		/* Get the timestamp overflow counters */
+		current_time_sum2 = readl(cygnus_dte->audioeav +
+			DTE_NCO_TIME_REG_BASE);
+		current_time_sum3 = readl(cygnus_dte->audioeav +
+			DTE_NCO_OVERFLOW_REG_BASE);
+		spin_unlock(&cygnus_dte->lock);
+
+		/*
+		 * Timestamp overflow only includes the bottom
+		 * 8 bits of sum3 and the top 4 bits of sum2.
+		 *
+		 * Combine sum3 and sum2 to the timestamp_overflow.
+		 * Units of 2^32 ns = 4.294967296 sec
+		 */
+		timestamp_overflow =
+			((current_time_sum3 & 0xff) << 4) |
+			((current_time_sum2 >> 28) & 0xffffff);
+		/*
+		 * If the current time is less than the fifo timestamped time
+		 * then wrap around must have occurred.
+		 * Convert current_time_sum2 to ns before comparison
+		 */
+		if ((current_time_sum2 << 4) <= timestamp_ns) {
+			if (timestamp_overflow > 0)
+				/*
+				 * Decrement the overflow counter since the fifo
+				 * timestamp occurred before overflow counter
+				 * had incremented
+				 */
+				timestamp_overflow--;
+			else
+				/*
+				 * If the overflow counter is zero *and*
+				 * the timestamp wrapped then the overflow
+				 * counter also wrapped.
+				 * Set to max value of 12 bits wide.
+				 * sum3 (8 bits) and sum2 (top 4 bits)
+				 */
+				timestamp_overflow = 0xfff;
+		}
+
+		spin_lock(&cygnus_dte->lock);
+		/*
+		 * Update reference timespec if wrap of the timestamp overflow
+		 * occurred
+		 */
+		if (timestamp_overflow < cygnus_dte->timestamp_overflow_last) {
+			struct timespec ts_wrapamount;
+			/*
+			 * Wrap around occurred.
+			 * Full wrap around amount is 44 bits in ns.
+			 * Includes sum3 (8bits), sum2 (32 bits), sum1 (4 bits)
+			 * Precisely 17592.186044416 sec = ~4.887 hrs
+			 */
+			ts_wrapamount = ns_to_timespec((uint64_t)0x1<<44);
+			/* Increment the reference */
+			cygnus_dte->ts_ref =
+				timespec_add(cygnus_dte->ts_ref, ts_wrapamount);
+		}
+		/* Track the last timestamp overflow value */
+		cygnus_dte->timestamp_overflow_last = timestamp_overflow;
+
+		/* Convert current timestamp(ns) to timespec */
+		ns = ((uint64_t)timestamp_overflow << 32) +
+			timestamp_ns;
+		ts_stamp = ns_to_timespec(ns);
+
+		/* Add the current time to the reference time */
+		ts_stamp = timespec_add(cygnus_dte->ts_ref, ts_stamp);
+		spin_unlock(&cygnus_dte->lock);
+
+		/*
+		 * The valid timestamp may be valid for more than one client
+		 * Examine all the active clients and insert timestamp
+		 * to the appropriate software fifo for each client asserted
+		 * Clients[0:3] are unused.
+		 * Shift down by 4 to zero index the first valid input client
+		 */
+		active_clients >>= 4;
+
+		for_each_set_bit(client,
+			(unsigned long *)&active_clients, DTE_CLIENT_MAX) {
+			/* Insert full timestamps into software fifo */
+			inlen = kfifo_in(
+				&cygnus_dte->recv_fifo[client],
+				&ts_stamp,
+				sizeof(struct timespec));
+
+			if (inlen != sizeof(struct timespec)) {
+				cygnus_dte->kfifoof[client]++;
+				dev_err(&cygnus_dte->pdev->dev,
+					"kfifoof[%d] = %d\n",
+					client,
+					cygnus_dte->kfifoof[client]);
+			}
+		}
+	} while (++i < DTE_HW_FIFO_SIZE); /* at most get FIFO size */
+
+	if (fifo_csr & BIT(DTE_LTS_CSR_REG__FIFO_UNDERFLOW)) {
+		cygnus_dte->fifouf++;
+		dev_err(&cygnus_dte->pdev->dev,
+			"fifouf = %d\n", cygnus_dte->fifouf);
+	}
+	if (fifo_csr & BIT(DTE_LTS_CSR_REG__FIFO_OVERFLOW)) {
+		cygnus_dte->fifoof++;
+		dev_err(&cygnus_dte->pdev->dev,
+			"fifoof =%d\n", cygnus_dte->fifoof);
+	}
+
+	/* overflow/underflow error, reset fifo */
+	if (fifo_csr & 0xc) {
+		writel(0, cygnus_dte->audioeav + DTE_LTS_CSR_REG_BASE);
+		dev_err(&cygnus_dte->pdev->dev, "Resetting HW FIFO\n");
+	}
+
+	return IRQ_HANDLED;
+}
+
+static const struct of_device_id bcm_cygnus_dte_of_match[] = {
+	{ .compatible = "brcm,cygnus-dte", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bcm_cygnus_dte_of_match);
+
+static int bcm_cygnus_dte_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct bcm_cygnus_dte *cygnus_dte;
+	int ret = -ENOMEM;
+	dev_t devt;
+	struct device *retdev;
+	struct class *dte_class;
+	int client;
+	int irq;
+
+	dev_info(dev, "Entering bcm_cygnus_dte_probe\n");
+
+	cygnus_dte = devm_kzalloc(dev, sizeof(*cygnus_dte), GFP_KERNEL);
+	if (!cygnus_dte)
+		return -ENOMEM;
+
+	mutex_init(&cygnus_dte->mutex);
+
+	/* Audio DTE memory mapped registers */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	cygnus_dte->audioeav = devm_ioremap_resource(dev, res);
+	if (IS_ERR(cygnus_dte->audioeav)) {
+		dev_err(&pdev->dev, "%s IO remap audioeav failed\n", __func__);
+		return PTR_ERR(cygnus_dte->audioeav);
+	}
+
+	spin_lock_init(&cygnus_dte->lock);
+
+	/* get interrupt */
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "%s platform_get_irq failed\n", __func__);
+		return -ENODEV;
+	}
+
+	ret = devm_request_threaded_irq(&pdev->dev, irq,
+				NULL, bcm_cygnus_dte_isr_threaded,
+				IRQF_ONESHOT, pdev->name, pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "request_irq error %d\n", ret);
+		return ret;
+	}
+
+	for (client = DTE_CLIENT_MIN; client < DTE_CLIENT_MAX; client++) {
+		ret = kfifo_alloc(&cygnus_dte->recv_fifo[client],
+				DTE_SW_FIFO_SIZE * sizeof(struct timespec),
+				GFP_KERNEL);
+		if (ret) {
+			dev_err(dev, "Failed kfifo alloc\n");
+			goto err_free_kfifo;
+		}
+	}
+
+	/* create device */
+	cdev_init(&cygnus_dte->dte_cdev, &dte_cdev_fops);
+
+	cygnus_dte->pdev = pdev;
+
+	/* create class for mdev auto create node /dev/dte */
+	dte_class = class_create(THIS_MODULE, DTE_DEVICE_NAME);
+	if (IS_ERR(dte_class)) {
+		ret = PTR_ERR(dte_class);
+		dev_err(&pdev->dev, "can't register static dte class\n");
+		goto err_free_kfifo;
+	}
+	cygnus_dte->dte_class = dte_class;
+
+	ret = alloc_chrdev_region(&devt, 0, DTE_MAX_DEVS, DTE_DEVICE_NAME);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"%s Alloc char device region failed\n", __func__);
+		goto err_class_destroy;
+	}
+
+	ret = cdev_add(&cygnus_dte->dte_cdev, devt, 1);
+	if (ret) {
+		dev_err(dev, "Failed adding dte cdev file\n");
+		goto err_unreg_chardev;
+	}
+
+	retdev = device_create(cygnus_dte->dte_class, NULL,
+			devt, NULL, DTE_DEVICE_NAME);
+	if (IS_ERR(retdev)) {
+		dev_err(&pdev->dev, "can't create dte device\n ");
+		ret = PTR_ERR(retdev);
+		goto err_cdev_delete;
+	}
+
+	list_add_tail(&cygnus_dte->node, &dtedev_list);
+	platform_set_drvdata(pdev, cygnus_dte);
+	cygnus_dte->devt = devt;
+
+	dev_info(dev, "Exiting bcm_cygnus_dte_probe\n");
+
+	return 0;
+
+err_cdev_delete:
+	cdev_del(&cygnus_dte->dte_cdev);
+err_unreg_chardev:
+	unregister_chrdev_region(devt, DTE_MAX_DEVS);
+err_class_destroy:
+	class_destroy(cygnus_dte->dte_class);
+err_free_kfifo:
+	for (client = DTE_CLIENT_MIN; client < DTE_CLIENT_MAX; client++)
+		kfifo_free(&cygnus_dte->recv_fifo[client]);
+	return ret;
+}
+
+static int bcm_cygnus_dte_remove(struct platform_device *pdev)
+{
+	int client;
+	int intr_mask;
+	struct bcm_cygnus_dte *cygnus_dte = platform_get_drvdata(pdev);
+
+	/* disable isochronous interrupt */
+	intr_mask = readl(cygnus_dte->audioeav + DTE_INTR_MASK_REG);
+	intr_mask |= 1 << ISO_INTR_MASK_SHIFT;
+	writel(intr_mask, cygnus_dte->audioeav + DTE_INTR_MASK_REG);
+
+	list_del(&cygnus_dte->node);
+	device_destroy(cygnus_dte->dte_class, cygnus_dte->devt);
+	class_destroy(cygnus_dte->dte_class);
+	unregister_chrdev_region(cygnus_dte->devt, DTE_MAX_DEVS);
+	platform_set_drvdata(pdev, NULL);
+	cdev_del(&cygnus_dte->dte_cdev);
+	for (client = DTE_CLIENT_MIN; client < DTE_CLIENT_MAX; client++)
+		kfifo_free(&cygnus_dte->recv_fifo[client]);
+
+	return 0;
+}
+
+static struct platform_driver bcm_cygnus_dte_driver = {
+	.driver = {
+		.name = "cygnus-dte",
+		.of_match_table = bcm_cygnus_dte_of_match,
+	},
+	.probe    = bcm_cygnus_dte_probe,
+	.remove   = bcm_cygnus_dte_remove,
+};
+module_platform_driver(bcm_cygnus_dte_driver);
+
+MODULE_AUTHOR("Broadcom");
+MODULE_DESCRIPTION("Broadcom Cygnus DTE Digital Timing Engine driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/bcm_cygnus_dte.h b/include/linux/bcm_cygnus_dte.h
new file mode 100644
index 0000000..6d785c2
--- /dev/null
+++ b/include/linux/bcm_cygnus_dte.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015, Broadcom Corporation. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#ifndef _BCM_CYGNUS_DTE_H_
+#define _BCM_CYGNUS_DTE_H_
+
+/**
+ * DTE Client
+ */
+enum dte_client {
+	DTE_CLIENT_MIN = 0,
+	DTE_CLIENT_I2S0_BITCLOCK = 0,
+	DTE_CLIENT_I2S1_BITCLOCK,
+	DTE_CLIENT_I2S2_BITCLOCK,
+	DTE_CLIENT_I2S0_WORDCLOCK,
+	DTE_CLIENT_I2S1_WORDCLOCK,
+	DTE_CLIENT_I2S2_WORDCLOCK,
+	DTE_CLIENT_LCD_CLFP,
+	DTE_CLIENT_LCD_CLLP,
+	DTE_CLIENT_GPIO14,
+	DTE_CLIENT_GPIO15,
+	DTE_CLIENT_GPIO22,
+	DTE_CLIENT_GPIO23,
+	DTE_CLIENT_MAX,
+};
+
+#define DTE_IOCTL_BASE          'd'
+#define DTE_IO(nr)              _IO(DTE_IOCTL_BASE, nr)
+#define DTE_IOR(nr, type)       _IOR(DTE_IOCTL_BASE, nr, type)
+#define DTE_IOW(nr, type)       _IOW(DTE_IOCTL_BASE, nr, type)
+#define DTE_IOWR(nr, type)      _IOWR(DTE_IOCTL_BASE, nr, type)
+
+#define DTE_IOCTL_SET_DIVIDER       DTE_IOW(0x00, struct dte_data)
+#define DTE_IOCTL_ENABLE_TIMESTAMP  DTE_IOW(0x01, struct dte_data)
+#define DTE_IOCTL_SET_IRQ_INTERVAL  DTE_IOW(0x02, struct dte_data)
+#define DTE_IOCTL_GET_TIMESTAMP     DTE_IOWR(0x03, struct dte_timestamp)
+#define DTE_IOCTL_SET_TIME          DTE_IOW(0x04, struct timespec)
+#define DTE_IOCTL_GET_TIME          DTE_IOR(0x05, struct timespec)
+#define DTE_IOCTL_ADJ_TIME          DTE_IOW(0x06, int64_t)
+#define DTE_IOCTL_ADJ_FREQ          DTE_IOW(0x07, int32_t)
+
+struct dte_data {
+	enum dte_client client;
+	unsigned int data;
+};
+
+struct dte_timestamp {
+	enum dte_client client;
+	struct timespec ts;
+};
+
+struct bcm_cygnus_dte;
+
+extern struct bcm_cygnus_dte *dte_get_dev_from_devname(
+		const char *devname);
+extern int dte_enable_timestamp(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		int enable);
+extern int dte_set_client_divider(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		int divider);
+extern int dte_set_irq_interval(
+		struct bcm_cygnus_dte *cygnus_dte,
+		uint32_t nanosec);
+extern int dte_get_timestamp(
+		struct bcm_cygnus_dte *cygnus_dte,
+		enum dte_client client,
+		struct timespec *ts);
+extern int dte_set_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		struct timespec *ts);
+extern int dte_get_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		struct timespec *ts);
+extern int dte_adj_time(
+		struct bcm_cygnus_dte *cygnus_dte,
+		int64_t delta);
+extern int dte_adj_freq(
+		struct bcm_cygnus_dte *cygnus_dte,
+		int32_t ppb);
+#endif
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list