From: jonathar@broadcom.com (Jonathan Richardson)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH 2/2] misc: Add initial Digital Timing Engine (DTE) driver for cygnus
Date: Wed, 22 Apr 2015 16:22:03 -0700 [thread overview]
Message-ID: <1429744923-2007-3-git-send-email-jonathar@broadcom.com> (raw)
In-Reply-To: <1429744923-2007-1-git-send-email-jonathar@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Tested-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Jonathan Richardson <jonathar@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); /*@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
WARNING: multiple messages have this Message-ID (diff)
From: Jonathan Richardson <jonathar@broadcom.com>
To: Scott Branden <sbranden@broadcom.com>,
Darren Edamura <dedamura@broadcom.com>,
Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>,
Mark Rutland <mark.rutland@arm.com>,
Ian Campbell <ijc+devicetree@hellion.org.uk>,
Kumar Gala <galak@codeaurora.org>, Ray Jui <rjui@broadcom.com>,
Arnd Bergmann <arnd@arndb.de>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
bcm-kernel-feedback-list@broadcom.com,
linux-kernel@vger.kernel.org,
Jonathan Richardson <jonathar@broadcom.com>
Subject: [PATCH 2/2] misc: Add initial Digital Timing Engine (DTE) driver for cygnus
Date: Wed, 22 Apr 2015 16:22:03 -0700 [thread overview]
Message-ID: <1429744923-2007-3-git-send-email-jonathar@broadcom.com> (raw)
In-Reply-To: <1429744923-2007-1-git-send-email-jonathar@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Tested-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Jonathan Richardson <jonathar@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
WARNING: multiple messages have this Message-ID (diff)
From: Jonathan Richardson <jonathar@broadcom.com>
To: Scott Branden <sbranden@broadcom.com>,
Darren Edamura <dedamura@broadcom.com>,
Rob Herring <robh+dt@kernel.org>, Pawel Moll <pawel.moll@arm.com>,
Mark Rutland <mark.rutland@arm.com>,
Ian Campbell <ijc+devicetree@hellion.org.uk>,
Kumar Gala <galak@codeaurora.org>, Ray Jui <rjui@broadcom.com>,
Arnd Bergmann <arnd@arndb.de>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: <devicetree@vger.kernel.org>,
<linux-arm-kernel@lists.infradead.org>,
<bcm-kernel-feedback-list@broadcom.com>,
<linux-kernel@vger.kernel.org>,
Jonathan Richardson <jonathar@broadcom.com>
Subject: [PATCH 2/2] misc: Add initial Digital Timing Engine (DTE) driver for cygnus
Date: Wed, 22 Apr 2015 16:22:03 -0700 [thread overview]
Message-ID: <1429744923-2007-3-git-send-email-jonathar@broadcom.com> (raw)
In-Reply-To: <1429744923-2007-1-git-send-email-jonathar@broadcom.com>
Reviewed-by: Scott Branden <sbranden@broadcom.com>
Tested-by: Scott Branden <sbranden@broadcom.com>
Signed-off-by: Jonathan Richardson <jonathar@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
next prev parent reply other threads:[~2015-04-22 23:22 UTC|newest]
Thread overview: 67+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-04-22 23:22 [PATCH 0/2] Add DTE driver for Cygnus Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson
2015-04-22 23:22 ` [PATCH 1/2] misc: Add DT binding for cygnus Digital Timing Engine (DTE) driver Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson [this message]
2015-04-22 23:22 ` [PATCH 2/2] misc: Add initial Digital Timing Engine (DTE) driver for cygnus Jonathan Richardson
2015-04-22 23:22 ` Jonathan Richardson
2015-04-23 8:04 ` Arnd Bergmann
2015-04-23 8:04 ` Arnd Bergmann
2015-04-23 8:04 ` Arnd Bergmann
2015-04-23 18:07 ` Jonathan Richardson
2015-04-23 18:07 ` Jonathan Richardson
2015-04-23 18:07 ` Jonathan Richardson
2015-05-01 19:01 ` Jonathan Richardson
2015-05-01 19:01 ` Jonathan Richardson
2015-05-01 19:01 ` Jonathan Richardson
2015-05-01 19:30 ` One Thousand Gnomes
2015-05-01 19:30 ` One Thousand Gnomes
2015-05-01 19:30 ` One Thousand Gnomes
2015-05-01 19:40 ` Arnd Bergmann
2015-05-01 19:40 ` Arnd Bergmann
2015-05-08 20:02 ` Jonathan Richardson
2015-05-08 20:02 ` Jonathan Richardson
2015-05-08 20:02 ` Jonathan Richardson
2015-05-13 1:03 ` Jonathan Richardson
2015-05-13 1:03 ` Jonathan Richardson
2015-05-13 1:03 ` Jonathan Richardson
2015-05-13 12:19 ` Arnd Bergmann
2015-05-13 12:19 ` Arnd Bergmann
2015-05-13 14:37 ` Richard Cochran
2015-05-13 14:37 ` Richard Cochran
2015-05-13 14:51 ` Richard Cochran
2015-05-13 14:51 ` Richard Cochran
2015-05-13 15:35 ` Richard Cochran
2015-05-13 15:35 ` Richard Cochran
2015-05-13 19:50 ` Jonathan Richardson
2015-05-13 19:50 ` Jonathan Richardson
2015-05-13 19:50 ` Jonathan Richardson
2015-05-13 20:27 ` Richard Cochran
2015-05-13 20:27 ` Richard Cochran
2015-05-13 23:25 ` Jonathan Richardson
2015-05-13 23:25 ` Jonathan Richardson
2015-05-13 23:25 ` Jonathan Richardson
2015-05-14 11:30 ` Richard Cochran
2015-05-14 11:30 ` Richard Cochran
2015-05-14 11:30 ` Richard Cochran
2015-05-20 23:38 ` Jonathan Richardson
2015-05-20 23:38 ` Jonathan Richardson
2015-05-20 23:38 ` Jonathan Richardson
2015-05-21 6:33 ` Richard Cochran
2015-05-21 6:33 ` Richard Cochran
2015-05-21 17:48 ` Jonathan Richardson
2015-05-21 17:48 ` Jonathan Richardson
2015-05-21 17:48 ` Jonathan Richardson
2015-05-13 15:21 ` Richard Cochran
2015-05-13 15:21 ` Richard Cochran
2015-05-13 15:21 ` Richard Cochran
2015-05-13 19:38 ` Jonathan Richardson
2015-05-13 19:38 ` Jonathan Richardson
2015-05-13 19:38 ` Jonathan Richardson
2015-05-13 19:42 ` Richard Cochran
2015-05-13 19:42 ` Richard Cochran
2015-05-13 19:39 ` Jonathan Richardson
2015-05-13 19:39 ` Jonathan Richardson
2015-05-13 19:39 ` Jonathan Richardson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1429744923-2007-3-git-send-email-jonathar@broadcom.com \
--to=jonathar@broadcom.com \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.