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