Reviewed-by: Scott Branden <sbranden@xxxxxxxxxxxx> Tested-by: Scott Branden <sbranden@xxxxxxxxxxxx> Signed-off-by: Jonathan Richardson <jonathar@xxxxxxxxxxxx> --- 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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html