From: Yonglong Liu liuyonglong@huawei.com
driver inclusion category:feature bugzilla: https://gitee.com/openeuler/kernel/issues/I9A3QT CVE: NA
----------------------------------------------------------------------
The hns3 driver provide ptp driver to get 1588 clock from ethernet, but only the first PF on main chip can support this, so, if getting ptp time from other chip, may have some bus latency. The PTP sync device use to eliminate the bus latency.
Signed-off-by: Yonglong Liu liuyonglong@huawei.com Signed-off-by: Jiantao Xiao xiaojiantao1@h-partners.com --- MAINTAINERS | 5 + arch/arm64/configs/openeuler_defconfig | 1 + drivers/ptp/Kconfig | 11 + drivers/ptp/Makefile | 1 + drivers/ptp/ptp_hisi.c | 1027 ++++++++++++++++++++++++ 5 files changed, 1045 insertions(+) create mode 100644 drivers/ptp/ptp_hisi.c
diff --git a/MAINTAINERS b/MAINTAINERS index f0dd63afe9e3..a3dc0b545d9a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9532,6 +9532,11 @@ S: Maintained F: Documentation/ABI/testing/debugfs-hisi-zip F: drivers/crypto/hisilicon/zip/
+HISILICON HNS3 PTP SYNC DRIVER +M: Yonglong Liu liuyonglong@huawei.com +S: Supported +F: drivers/ptp/ptp_hisi.c + HMM - Heterogeneous Memory Management M: Jérôme Glisse jglisse@redhat.com L: linux-mm@kvack.org diff --git a/arch/arm64/configs/openeuler_defconfig b/arch/arm64/configs/openeuler_defconfig index 492fecf80dfa..beaeac96796b 100644 --- a/arch/arm64/configs/openeuler_defconfig +++ b/arch/arm64/configs/openeuler_defconfig @@ -3842,6 +3842,7 @@ CONFIG_PTP_1588_CLOCK_KVM=y # CONFIG_PTP_1588_CLOCK_IDTCM is not set # CONFIG_PTP_1588_CLOCK_MOCK is not set # CONFIG_PTP_1588_CLOCK_OCP is not set +CONFIG_PTP_HISI=m # end of PTP clock support
CONFIG_PINCTRL=y diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index ed9d97a032f1..14a7b49e4760 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -211,4 +211,15 @@ config PTP_DFL_TOD To compile this driver as a module, choose M here: the module will be called ptp_dfl_tod.
+config PTP_HISI + tristate "HiSilicon PTP sync platform driver" + help + PTP sync driver work on multichip system, eliminates the bus latency + between multichip, and provide a higher precision clock source. But + the clock source of PTP sync device is from the RTC of HNS3 ethernet + device, so, if you want the PTP sync device works, you must enable + HNS3 driver also. + + If unsure, say N. + endmenu diff --git a/drivers/ptp/Makefile b/drivers/ptp/Makefile index dea0cebd2303..8d21ee20787d 100644 --- a/drivers/ptp/Makefile +++ b/drivers/ptp/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_PTP_1588_CLOCK_MOCK) += ptp_mock.o obj-$(CONFIG_PTP_1588_CLOCK_VMW) += ptp_vmw.o obj-$(CONFIG_PTP_1588_CLOCK_OCP) += ptp_ocp.o obj-$(CONFIG_PTP_DFL_TOD) += ptp_dfl_tod.o +obj-$(CONFIG_PTP_HISI) += ptp_hisi.o diff --git a/drivers/ptp/ptp_hisi.c b/drivers/ptp/ptp_hisi.c new file mode 100644 index 000000000000..0eb1ebf44dc8 --- /dev/null +++ b/drivers/ptp/ptp_hisi.c @@ -0,0 +1,1027 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright (c) 2022 Hisilicon Limited. +#include <linux/mm.h> +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/ptp_clock_kernel.h> +#include <linux/net_tstamp.h> +#include <linux/debugfs.h> + +#ifndef PTP_CLOCK_NAME_LEN +#define PTP_CLOCK_NAME_LEN 32 +#endif + +#define HISI_PTP_VERSION "22.10.2" + +#define HISI_PTP_NAME "hisi_ptp" +#define HISI_PTP_INT_NAME_LEN 32 + +#define HISI_PTP_DBGFS_STS_LEN 2048 +#define HISI_PTP_DBGFS_REG_LEN 0x10000 + +#define HISI_RES_T_PERI_SC 0 +#define HISI_RES_N_NET_SC 0 +#define HISI_RES_N_IO_SC 1 + +#define HISI_PTP_INIT_DONE 0 + +/* peri subctrl reg offset */ +#define PERI_SC_PTP_RESET_REQ 0xE18 +#define PERI_SC_PTP_RESET_DREQ 0xE1C +#define PERI_SC_LOCAL_TIMER_COMP_HIGH_ADDR 0x5000 +#define PERI_SC_LOCAL_TIMER_COMP_LOW_ADDR 0x5004 +#define PERI_SC_BAUD_VALUE_ADDR 0x5008 +#define PERI_SC_LOCAL_CNT_EN_ADDR 0x500C +#define PERI_SC_SYNC_ERR_COMP_HIGH_ADDR 0x5010 +#define PERI_SC_SYNC_ERR_COMP_LOW_ADDR 0x5014 +#define PERI_SC_CRC_EN_ADDR 0x5018 +#define PERI_SC_ONE_CYCLE_NUM_ADDR 0x5020 +#define PERI_SC_SYNC_ERR_CLR_ADDR 0x5024 +#define PERI_SC_RX_SHIFT_EN_ADDR 0x5028 +#define PERI_SC_TIMEL_CY_NUM_ADDR 0x502C +#define PERI_SC_INT_PTP_SYNC_ERR_ADDR 0x5044 +#define PERI_SC_INT_PTP_SYNC_ERR_MASK_ADDR 0x5048 +#define PERI_SC_INT_ORIGIN 0x504C +#define PERI_SC_CRC_ERR_COUNT 0x5050 +#define PERI_SC_CRC_INT_CONTRL_ADDR 0x5054 +#define PERI_SC_CAPTURE_PTP_TIME_COMP_HIGH 0x5058 +#define PERI_SC_CAPTURE_PTP_TIME_COMP_LOW 0x505C +#define PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_HIGH 0x5060 +#define PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_LOW 0x5064 +#define PERI_SC_CAPTURE_VLD 0x5068 +#define PERI_SC_LOCAL_TIME_LOW_ADDR 0x5070 +#define PERI_SC_LOCAL_TIME_HIGH_ADDR 0x5074 + +/* net subctrl reg offset */ +#define NET_SC_PTP_BAUD_VALUE_ADDR 0x2008 +#define NET_SC_PTP_COUNTER_EN_ADDR 0x200C +#define NET_SC_PTP_NORMAL_MODE_EN 0x2010 +#define NET_SC_PTP_WIRE_DELAY_CAL_EN 0x2014 +#define NET_SC_SAMPLE_DELAY_CFG_ADDR 0x2018 +#define NET_SC_PTP_TX_DFXBUS0_ADDR 0x201C +#define NET_SC_PTP_TX_DFXBUS1_ADDR 0x2020 +#define NET_SC_PTP_TX_DFXBUS2_ADDR 0x2024 +#define NET_SC_PTP_TX_DFXBUS3_ADDR 0x2028 + +/* io subctrl reg offset */ +#define IO_SC_PTP_BAUD_VALUE_ADDR 0x2008 +#define IO_SC_PTP_COUNTER_EN_ADDR 0x200C +#define IO_SC_PTP_NORMAL_MODE_EN 0x2010 +#define IO_SC_PTP_WIRE_DELAY_CAL_EN 0x2014 +#define IO_SC_SAMPLE_DELAY_CFG_ADDR 0x2018 +#define IO_SC_PTP_TX_DFXBUS0_ADDR 0x201C +#define IO_SC_PTP_TX_DFXBUS1_ADDR 0x2020 +#define IO_SC_PTP_TX_DFXBUS2_ADDR 0x2024 +#define IO_SC_PTP_TX_DFXBUS3_ADDR 0x2028 + +/* default values */ +#define HISI_DEF_BAUD 0x1388 +#define HISI_DEF_TIME_COMP 0xB2432 +#define HISI_DEF_ERR_COMP 0xFFFFFFFF +#define HISI_DEF_ONE_CYCLE_NUM 0x50 + +#define HISI_PTP_TX_IDLE_MASK GENMASK(26, 23) + +#define HISI_PTP_RX_CRC_INT_EN BIT(0) +#define HISI_PTP_RX_CRC_CLR BIT(1) +#define HISI_PTP_RX_CRC_CLR_AND_EN \ + (HISI_PTP_RX_CRC_INT_EN | HISI_PTP_RX_CRC_INT_EN) +#define HISI_PTP_RX_CRC_CLR_AND_DISABLE HISI_PTP_RX_CRC_CLR + +#define HISI_PTP_SUP_CHK_CNT 32 +/* suppress check window and suppress time, unit: ms */ +#define HISI_PTP_SUP_CHK_THR 10 +#define HISI_PTP_SUP_TIME 100 + +enum HISI_PTP_TX_MODE { + HISI_PTP_CAL_MODE, + HISI_PTP_NORMAL_MODE, +}; + +struct hisi_ptp_rx { + struct list_head node; + char name[HISI_PTP_INT_NAME_LEN]; + struct device *dev; + u64 time_comp; /* internal wire time compensation value */ + int irq; + void __iomem *base; +}; + +struct hisi_ptp_tx { + struct device *dev; + void __iomem *base; + void __iomem *io_sc_base; +}; + +struct hisi_ptp_pdev { + struct list_head ptp_rx_list; + struct hisi_ptp_tx *ptp_tx; + u32 tx_cnt; + u32 rx_total; + u32 rx_cnt; + unsigned long flag; + void __iomem *rx_base; /* peri subctl base of chip 0 */ + u32 irq_cnt; + unsigned long last_jiffies; /* record last irq jiffies */ + struct timer_list suppress_timer; + struct ptp_clock *clock; + struct ptp_clock_info info; + rwlock_t rw_lock; + struct dentry *dbgfs_root; +}; + +struct hisi_ptp_reg { + const char *name; + u32 offset; +}; + +static struct hisi_ptp_pdev g_ptpdev; + +static uint err_threshold = HISI_DEF_ERR_COMP; +module_param(err_threshold, uint, 0644); +MODULE_PARM_DESC(err_threshold, "PTP time sync error threshold"); + +static struct hisi_ptp_pdev *hisi_ptp_get_pdev(struct ptp_clock_info *info) +{ + struct hisi_ptp_pdev *ptp = + container_of(info, struct hisi_ptp_pdev, info); + return ptp; +} + +/* This function should call under rw_lock */ +static void hisi_ptp_disable(struct hisi_ptp_pdev *ptp) +{ + struct hisi_ptp_rx *rx; + void __iomem *base; + + /* disable tx */ + if (ptp->ptp_tx && ptp->ptp_tx->base) { + base = ptp->ptp_tx->base; + writel(0, base + NET_SC_PTP_COUNTER_EN_ADDR); + } + + /* disable all totem rx and interrupt */ + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(0, base + PERI_SC_RX_SHIFT_EN_ADDR); + writel(1, base + PERI_SC_INT_PTP_SYNC_ERR_MASK_ADDR); + writel(HISI_PTP_RX_CRC_CLR_AND_DISABLE, + base + PERI_SC_CRC_INT_CONTRL_ADDR); + } +} + +/* This function should call under rw_lock */ +static void hisi_ptp_unmask_irq(struct hisi_ptp_pdev *ptp) +{ + struct hisi_ptp_rx *rx; + void __iomem *base; + + /* clear CRC errors and unmask all totem interrupt */ + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(HISI_PTP_RX_CRC_INT_EN, + base + PERI_SC_CRC_INT_CONTRL_ADDR); + writel(0, base + PERI_SC_INT_PTP_SYNC_ERR_MASK_ADDR); + } +} + +/* This function should call under rw_lock */ +static void hisi_ptp_wait_and_enable(struct hisi_ptp_pdev *ptp) +{ +#define HISI_PTP_TX_IDLE_WAIT_CNT 20 + void __iomem *nimbus_base; + struct hisi_ptp_rx *rx; + void __iomem *base; + int delay_cnt = 0; + + if (!ptp->ptp_tx || !ptp->ptp_tx->base) + return; + + /* wait for tx idle */ + nimbus_base = ptp->ptp_tx->base; + while (delay_cnt++ < HISI_PTP_TX_IDLE_WAIT_CNT) { + u32 dfx_bus0 = readl(nimbus_base + NET_SC_PTP_TX_DFXBUS0_ADDR); + + /* wait bit26:23 to 0 */ + if ((dfx_bus0 & HISI_PTP_TX_IDLE_MASK) == 0) + break; + + udelay(1); + } + + /* enable all totem interrupt and rx */ + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(1, base + PERI_SC_SYNC_ERR_CLR_ADDR); + writel(0, base + PERI_SC_SYNC_ERR_CLR_ADDR); + writel(1, base + PERI_SC_INT_PTP_SYNC_ERR_ADDR); + writel(1, base + PERI_SC_RX_SHIFT_EN_ADDR); + } + + /* enable tx */ + writel(1, nimbus_base + NET_SC_PTP_COUNTER_EN_ADDR); + + hisi_ptp_unmask_irq(ptp); +} + +/* This function should call under rw_lock */ +static bool hisi_ptp_need_suppress(struct hisi_ptp_pdev *ptp) +{ + if (time_is_before_jiffies(ptp->last_jiffies + + msecs_to_jiffies(HISI_PTP_SUP_CHK_THR))) { + ptp->last_jiffies = jiffies; + ptp->irq_cnt = 0; + return false; + } + + if (ptp->irq_cnt++ < HISI_PTP_SUP_CHK_CNT) + return false; + + return true; +} + +static irqreturn_t hisi_ptp_irq_handle(int irq, void *data) +{ + struct hisi_ptp_pdev *ptp = (struct hisi_ptp_pdev *)data; + + dev_dbg(ptp->ptp_tx->dev, "ptp time sync error, irq:%d\n", irq); + + write_lock(&ptp->rw_lock); + + hisi_ptp_disable(ptp); + + if (hisi_ptp_need_suppress(ptp)) { + mod_timer(&ptp->suppress_timer, + jiffies + msecs_to_jiffies(HISI_PTP_SUP_TIME)); + write_unlock(&ptp->rw_lock); + return IRQ_HANDLED; + } + + hisi_ptp_wait_and_enable(ptp); + + write_unlock(&ptp->rw_lock); + + return IRQ_HANDLED; +} + +static int hisi_ptp_get_rx_resource(struct platform_device *pdev, + struct hisi_ptp_pdev *ptp) +{ + struct hisi_ptp_rx *rx; + struct resource *peri; + unsigned long flags; + u32 rx_total = 0; + bool is_base_rx; + int ret; + + ret = device_property_read_u32(&pdev->dev, "rx_num", &rx_total); + if (ret) { + dev_err(&pdev->dev, "failed to read rx total property\n"); + return ret; + } + + rx = devm_kzalloc(&pdev->dev, sizeof(struct hisi_ptp_rx), GFP_KERNEL); + if (!rx) + return -ENOMEM; + + peri = platform_get_resource(pdev, IORESOURCE_MEM, HISI_RES_T_PERI_SC); + if (!peri) { + dev_err(&pdev->dev, "failed to get rx peri resource\n"); + return -EINVAL; + } + + rx->base = devm_ioremap(&pdev->dev, peri->start, resource_size(peri)); + if (!rx->base) { + dev_err(&pdev->dev, "failed to remap rx peri resource\n"); + return -ENOMEM; + } + + rx->irq = platform_get_irq(pdev, 0); + if (rx->irq < 0) { + dev_err(&pdev->dev, "failed to get irq, ret = %d\n", rx->irq); + return rx->irq; + } + snprintf(rx->name, HISI_PTP_INT_NAME_LEN, "%s-%d", HISI_PTP_NAME, + rx->irq); + ret = devm_request_irq(&pdev->dev, rx->irq, hisi_ptp_irq_handle, 0, + rx->name, ptp); + if (ret) { + dev_err(&pdev->dev, "failed to request irq(%d), ret = %d\n", + rx->irq, ret); + return ret; + } + + is_base_rx = device_property_present(&pdev->dev, "base_rx"); + + rx->dev = &pdev->dev; + + write_lock_irqsave(&ptp->rw_lock, flags); + + if (is_base_rx) + ptp->rx_base = rx->base; + + ptp->rx_cnt++; + + /* use the first rx device to init the global rx_total */ + if (ptp->rx_total == 0) + ptp->rx_total = rx_total; + + if (ptp->rx_total != rx_total || ptp->rx_cnt > ptp->rx_total) { + write_unlock_irqrestore(&ptp->rw_lock, flags); + dev_err(&pdev->dev, + "failed to probe rx device, please check the asl file!\n"); + dev_err(&pdev->dev, + "rx_total:%u, current rx_total:%u, rx_cnt:%u\n", + ptp->rx_total, rx_total, ptp->rx_cnt); + + return -EINVAL; + } + + list_add_tail(&rx->node, &ptp->ptp_rx_list); + + write_unlock_irqrestore(&ptp->rw_lock, flags); + + return 0; +} + +static int hisi_ptp_get_tx_resource(struct platform_device *pdev, + struct hisi_ptp_pdev *ptp) +{ + struct hisi_ptp_tx *tx; + struct resource *mem; + unsigned long flags; + + write_lock_irqsave(&ptp->rw_lock, flags); + /* use have only one tx device */ + if (ptp->tx_cnt) { + write_unlock_irqrestore(&ptp->rw_lock, flags); + dev_err(&pdev->dev, + "failed to probe tx device, more than one tx device found, please check the asl file!\n"); + return -EINVAL; + } + write_unlock_irqrestore(&ptp->rw_lock, flags); + + tx = devm_kzalloc(&pdev->dev, sizeof(struct hisi_ptp_tx), GFP_KERNEL); + if (!tx) + return -ENOMEM; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, HISI_RES_N_NET_SC); + if (!mem) { + dev_err(&pdev->dev, "failed to get tx net sc resource\n"); + return -EINVAL; + } + + tx->base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem)); + if (!tx->base) { + dev_err(&pdev->dev, "failed to remap tx net sc resource\n"); + return -ENOMEM; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, HISI_RES_N_IO_SC); + if (!mem) { + dev_err(&pdev->dev, "failed to get tx nimbus io sc resource\n"); + return -EINVAL; + } + + tx->io_sc_base = devm_ioremap(&pdev->dev, mem->start, + resource_size(mem)); + if (!tx->io_sc_base) { + dev_err(&pdev->dev, "failed to remap tx nimbus io resource\n"); + return -ENOMEM; + } + + tx->dev = &pdev->dev; + + write_lock_irqsave(&ptp->rw_lock, flags); + ptp->tx_cnt++; + ptp->ptp_tx = tx; + write_unlock_irqrestore(&ptp->rw_lock, flags); + + return 0; +} + +static void hisi_ptp_cal_time_start(struct hisi_ptp_pdev *ptp) +{ + void __iomem *io_sc_base; + struct hisi_ptp_rx *rx; + void __iomem *base; + + /* config all rx to enter calculation mode. */ + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(1, base + PERI_SC_PTP_RESET_REQ); + writel(1, base + PERI_SC_PTP_RESET_DREQ); + writel(0, base + PERI_SC_LOCAL_TIMER_COMP_HIGH_ADDR); + writel(0, base + PERI_SC_LOCAL_TIMER_COMP_LOW_ADDR); + writel(1, base + PERI_SC_CRC_EN_ADDR); + writel(0, base + PERI_SC_LOCAL_CNT_EN_ADDR); + writel(1, base + PERI_SC_RX_SHIFT_EN_ADDR); + } + + /* config tx to enter calculation mode. */ + base = ptp->ptp_tx->base; + io_sc_base = ptp->ptp_tx->io_sc_base; + writel(HISI_PTP_CAL_MODE, io_sc_base + IO_SC_PTP_NORMAL_MODE_EN); + writel(HISI_PTP_CAL_MODE, base + NET_SC_PTP_NORMAL_MODE_EN); + + writel(HISI_DEF_BAUD, io_sc_base + IO_SC_PTP_BAUD_VALUE_ADDR); + writel(1, io_sc_base + IO_SC_PTP_COUNTER_EN_ADDR); + writel(0, io_sc_base + IO_SC_PTP_WIRE_DELAY_CAL_EN); + writel(1, io_sc_base + IO_SC_PTP_WIRE_DELAY_CAL_EN); +} + +static void hisi_ptp_cal_time_get(struct hisi_ptp_pdev *ptp) +{ +#define HISI_PTP_MAX_WAIT_CNT 60 + struct hisi_ptp_rx *rx; + void __iomem *base; + int cnt; + u32 rd_l; + u32 rd_h; + u32 td_l; + u32 td_h; + u64 rd; + u64 td; + + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + rx->time_comp = HISI_DEF_TIME_COMP; + + cnt = 0; + do { + if (readl(base + PERI_SC_CAPTURE_VLD) == 0) { + mdelay(1); + continue; + } + + rd_h = readl(base + + PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_HIGH); + rd_l = readl(base + + PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_LOW); + td_h = readl(base + PERI_SC_CAPTURE_PTP_TIME_COMP_HIGH); + td_l = readl(base + PERI_SC_CAPTURE_PTP_TIME_COMP_LOW); + + rd = (u64)rd_h << 32 | rd_l; + td = (u64)td_h << 32 | td_l; + + if (!rd || !td || rd < td) { + mdelay(1); + continue; + } + + rx->time_comp = rd - td; + break; + } while (cnt++ <= HISI_PTP_MAX_WAIT_CNT); + } +} + +static void hisi_ptp_cal_time_end(struct hisi_ptp_pdev *ptp) +{ + void __iomem *io_sc_base; + struct hisi_ptp_rx *rx; + void __iomem *base; + + /* config all rx to exit calculation mode. */ + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(0, base + PERI_SC_RX_SHIFT_EN_ADDR); + } + + /* config tx to exit calculation mode. */ + base = ptp->ptp_tx->base; + io_sc_base = ptp->ptp_tx->io_sc_base; + + writel(0, io_sc_base + IO_SC_PTP_COUNTER_EN_ADDR); + writel(HISI_PTP_NORMAL_MODE, io_sc_base + IO_SC_PTP_NORMAL_MODE_EN); + writel(HISI_PTP_NORMAL_MODE, base + NET_SC_PTP_NORMAL_MODE_EN); +} + +/* This function should call under rw_lock */ +static void hisi_ptp_cal_time_comp(struct hisi_ptp_pdev *ptp) +{ + hisi_ptp_cal_time_start(ptp); + hisi_ptp_cal_time_get(ptp); + hisi_ptp_cal_time_end(ptp); +} + +/* This function should call under rw_lock */ +static void hisi_ptp_peri_rx_init(struct hisi_ptp_pdev *ptp) +{ + struct hisi_ptp_rx *rx; + void __iomem *base; + + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + base = rx->base; + writel(1, base + PERI_SC_CRC_EN_ADDR); + writel(upper_32_bits(rx->time_comp), + base + PERI_SC_LOCAL_TIMER_COMP_HIGH_ADDR); + writel(lower_32_bits(rx->time_comp), + base + PERI_SC_LOCAL_TIMER_COMP_LOW_ADDR); + writel(err_threshold, + base + PERI_SC_SYNC_ERR_COMP_LOW_ADDR); + writel(1, base + PERI_SC_CRC_INT_CONTRL_ADDR); + writel(0, base + PERI_SC_SYNC_ERR_CLR_ADDR); + writel(1, base + PERI_SC_LOCAL_CNT_EN_ADDR); + writel(1, base + PERI_SC_RX_SHIFT_EN_ADDR); + } +} + +/* This function should call under rw_lock */ +static void hisi_ptp_net_tx_init(struct hisi_ptp_pdev *ptp) +{ + void __iomem *base; + + base = ptp->ptp_tx->base; + writel(1, base + NET_SC_PTP_COUNTER_EN_ADDR); +} + +static int hisi_ptp_adjfine(struct ptp_clock_info *ptp_info, long delta) +{ + return -EOPNOTSUPP; +} + +static int hisi_ptp_adjtime(struct ptp_clock_info *ptp_info, s64 delta) +{ + return -EOPNOTSUPP; +} + +static int hisi_ptp_settime(struct ptp_clock_info *ptp_info, + const struct timespec64 *ts) +{ + return -EOPNOTSUPP; +} + +static int hisi_ptp_gettime(struct ptp_clock_info *ptp_info, + struct timespec64 *ts, + struct ptp_system_timestamp *sts) +{ + struct hisi_ptp_pdev *ptp = hisi_ptp_get_pdev(ptp_info); + unsigned long flags; + u32 hi = UINT_MAX; + u32 lo = UINT_MAX; + u64 ns; + + read_lock_irqsave(&ptp->rw_lock, flags); + + if (ptp->rx_base) { + hi = readl(ptp->rx_base + PERI_SC_LOCAL_TIME_HIGH_ADDR); + lo = readl(ptp->rx_base + PERI_SC_LOCAL_TIME_LOW_ADDR); + } + + read_unlock_irqrestore(&ptp->rw_lock, flags); + + ns = (u64)hi * NSEC_PER_SEC + lo; + *ts = ns_to_timespec64(ns); + + return 0; +} + +static int hisi_ptp_create_clock(struct hisi_ptp_pdev *ptp) +{ + dev_info(ptp->ptp_tx->dev, "register ptp clock\n"); + + snprintf(ptp->info.name, PTP_CLOCK_NAME_LEN, "%s", HISI_PTP_NAME); + ptp->info.owner = THIS_MODULE; + ptp->info.adjfine = hisi_ptp_adjfine; + ptp->info.adjtime = hisi_ptp_adjtime; + ptp->info.settime64 = hisi_ptp_settime; + ptp->info.gettimex64 = hisi_ptp_gettime; + ptp->clock = ptp_clock_register(&ptp->info, ptp->ptp_tx->dev); + if (IS_ERR(ptp->clock)) { + dev_err(ptp->ptp_tx->dev, + "failed to register ptp clock, ret = %ld\n", + PTR_ERR(ptp->clock)); + return PTR_ERR(ptp->clock); + } + + return 0; +} + +static void hisi_ptp_timer(struct timer_list *t) +{ + struct hisi_ptp_pdev *ptp = from_timer(ptp, t, suppress_timer); + unsigned long flags; + + write_lock_irqsave(&ptp->rw_lock, flags); + + dev_dbg(ptp->ptp_tx->dev, "ptp timer timeout handler.\n"); + + ptp->last_jiffies = jiffies; + ptp->irq_cnt = 0; + + hisi_ptp_wait_and_enable(ptp); + + write_unlock_irqrestore(&ptp->rw_lock, flags); +} + +static int hisi_ptp_probe(struct platform_device *pdev) +{ + struct hisi_ptp_pdev *ptp = &g_ptpdev; + unsigned long flags; + const char *type; + int ret; + + dev_info(&pdev->dev, "ptp probe start\n"); + + ret = device_property_read_string(&pdev->dev, "type", &type); + if (ret) { + dev_err(&pdev->dev, "failed to read device type, ret = %d\n", + ret); + return ret; + } + + if (!memcmp(type, "rx", strlen("rx"))) { + ret = hisi_ptp_get_rx_resource(pdev, ptp); + } else if (!memcmp(type, "tx", strlen("tx"))) { + ret = hisi_ptp_get_tx_resource(pdev, ptp); + } else { + dev_err(&pdev->dev, + "failed to probe unknown device, type: %s\n", + type); + ret = -EINVAL; + } + if (ret) + return ret; + + write_lock_irqsave(&ptp->rw_lock, flags); + + if (ptp->rx_total == 0 || ptp->rx_total != ptp->rx_cnt || + ptp->tx_cnt != 1) { + write_unlock_irqrestore(&ptp->rw_lock, flags); + dev_info(&pdev->dev, + "waiting for devices...rx total:%u, now:%u. tx total:1, now:%u\n", + ptp->rx_total, ptp->rx_cnt, ptp->tx_cnt); + return 0; + } + + if (!ptp->rx_base) { + write_unlock_irqrestore(&ptp->rw_lock, flags); + dev_err(&pdev->dev, + "failed to probe, no base rx device, please check the asl file!\n"); + return -EINVAL; + } + + hisi_ptp_disable(ptp); + hisi_ptp_cal_time_comp(ptp); + hisi_ptp_peri_rx_init(ptp); + hisi_ptp_net_tx_init(ptp); + hisi_ptp_unmask_irq(ptp); + + write_unlock_irqrestore(&ptp->rw_lock, flags); + + ret = hisi_ptp_create_clock(ptp); + if (ret) { + write_lock_irqsave(&ptp->rw_lock, flags); + hisi_ptp_disable(ptp); + write_unlock_irqrestore(&ptp->rw_lock, flags); + return ret; + } + + set_bit(HISI_PTP_INIT_DONE, &ptp->flag); + + dev_info(&pdev->dev, "ptp probe end\n"); + return 0; +} + +static int hisi_ptp_remove(struct platform_device *pdev) +{ + struct hisi_ptp_pdev *ptp = &g_ptpdev; + struct hisi_ptp_rx *rx; + unsigned long flags; + + if (test_and_clear_bit(HISI_PTP_INIT_DONE, &ptp->flag)) { + ptp_clock_unregister(ptp->clock); + ptp->clock = NULL; + + write_lock_irqsave(&ptp->rw_lock, flags); + hisi_ptp_disable(ptp); + write_unlock_irqrestore(&ptp->rw_lock, flags); + + dev_info(&pdev->dev, "unregister ptp clock\n"); + } + + write_lock_irqsave(&ptp->rw_lock, flags); + if (ptp->ptp_tx && ptp->ptp_tx->dev == &pdev->dev) { + ptp->tx_cnt--; + ptp->ptp_tx = NULL; + dev_info(&pdev->dev, "remove tx ptp device\n"); + } else { + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + if (rx->dev == &pdev->dev) { + ptp->rx_cnt--; + list_del(&rx->node); + dev_info(&pdev->dev, "remove rx ptp device\n"); + break; + } + } + } + write_unlock_irqrestore(&ptp->rw_lock, flags); + + return 0; +} + +static const struct acpi_device_id hisi_ptp_acpi_match[] = { + { "HISI0411", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, hisi_ptp_acpi_match); + +static struct platform_driver hisi_ptp_driver = { + .probe = hisi_ptp_probe, + .remove = hisi_ptp_remove, + .driver = { + .name = HISI_PTP_NAME, + .acpi_match_table = ACPI_PTR(hisi_ptp_acpi_match), + }, +}; + +static ssize_t hisi_ptp_dbg_read_state(struct file *filp, char __user *buf, + size_t cnt, loff_t *ppos) +{ + struct hisi_ptp_pdev *ptp = filp->private_data; + struct hisi_ptp_rx *rx; + unsigned long flags; + ssize_t size = 0; + char *read_buf; + int pos = 0; + int len; + + if (*ppos < 0) + return -EINVAL; + if (cnt == 0) + return 0; + if (!access_ok(buf, cnt)) + return -EFAULT; + + read_buf = kvzalloc(HISI_PTP_DBGFS_STS_LEN, GFP_KERNEL); + if (!read_buf) + return -ENOMEM; + + len = HISI_PTP_DBGFS_STS_LEN; + + write_lock_irqsave(&ptp->rw_lock, flags); + pos += scnprintf(read_buf + pos, len - pos, "error threshold: %#x\n", + err_threshold); + pos += scnprintf(read_buf + pos, len - pos, "tx count: %u\n", + ptp->tx_cnt); + pos += scnprintf(read_buf + pos, len - pos, "rx total: %u\n", + ptp->rx_total); + pos += scnprintf(read_buf + pos, len - pos, "rx count: %u\n", + ptp->rx_cnt); + pos += scnprintf(read_buf + pos, len - pos, "irq count: %u\n", + ptp->irq_cnt); + pos += scnprintf(read_buf + pos, len - pos, "irq last jiffies: %lu\n", + ptp->last_jiffies); + + list_for_each_entry(rx, &ptp->ptp_rx_list, node) { + pos += scnprintf(read_buf + pos, len - pos, "name: %s\n", + rx->name); + pos += scnprintf(read_buf + pos, len - pos, "time comp: %#llx\n", + rx->time_comp); + pos += scnprintf(read_buf + pos, len - pos, "irq: %d\n", + rx->irq); + } + write_unlock_irqrestore(&ptp->rw_lock, flags); + + size = simple_read_from_buffer(buf, cnt, ppos, read_buf, + strlen(read_buf)); + + kvfree(read_buf); + + return size; +} + +static const struct hisi_ptp_reg hisi_ptp_tx_reg[] = { + {"NET_SC_PTP_BAUD_VALUE_ADDR ", + NET_SC_PTP_BAUD_VALUE_ADDR}, + {"NET_SC_PTP_COUNTER_EN_ADDR ", + NET_SC_PTP_COUNTER_EN_ADDR}, + {"NET_SC_PTP_NORMAL_MODE_EN ", + NET_SC_PTP_NORMAL_MODE_EN}, + {"NET_SC_PTP_WIRE_DELAY_CAL_EN", + NET_SC_PTP_WIRE_DELAY_CAL_EN}, + {"NET_SC_SAMPLE_DELAY_CFG_ADDR", + NET_SC_SAMPLE_DELAY_CFG_ADDR}, + {"NET_SC_PTP_TX_DFXBUS0_ADDR ", + NET_SC_PTP_TX_DFXBUS0_ADDR}, + {"NET_SC_PTP_TX_DFXBUS1_ADDR ", + NET_SC_PTP_TX_DFXBUS1_ADDR}, + {"NET_SC_PTP_TX_DFXBUS2_ADDR ", + NET_SC_PTP_TX_DFXBUS2_ADDR}, + {"NET_SC_PTP_TX_DFXBUS3_ADDR ", + NET_SC_PTP_TX_DFXBUS3_ADDR} +}; + +static const struct hisi_ptp_reg hisi_ptp_tx_io_reg[] = { + {"IO_SC_PTP_BAUD_VALUE_ADDR ", + IO_SC_PTP_BAUD_VALUE_ADDR}, + {"IO_SC_PTP_COUNTER_EN_ADDR ", + IO_SC_PTP_COUNTER_EN_ADDR}, + {"IO_SC_PTP_NORMAL_MODE_EN ", + IO_SC_PTP_NORMAL_MODE_EN}, + {"IO_SC_PTP_WIRE_DELAY_CAL_EN", + IO_SC_PTP_WIRE_DELAY_CAL_EN}, + {"IO_SC_SAMPLE_DELAY_CFG_ADDR", + IO_SC_SAMPLE_DELAY_CFG_ADDR}, + {"IO_SC_PTP_TX_DFXBUS0_ADDR ", + IO_SC_PTP_TX_DFXBUS0_ADDR}, + {"IO_SC_PTP_TX_DFXBUS1_ADDR ", + IO_SC_PTP_TX_DFXBUS1_ADDR}, + {"IO_SC_PTP_TX_DFXBUS2_ADDR ", + IO_SC_PTP_TX_DFXBUS2_ADDR}, + {"IO_SC_PTP_TX_DFXBUS3_ADDR ", + IO_SC_PTP_TX_DFXBUS3_ADDR} +}; + +static const struct hisi_ptp_reg hisi_ptp_rx_reg[] = { + {"PERI_SC_LOCAL_TIMER_COMP_HIGH_ADDR ", + PERI_SC_LOCAL_TIMER_COMP_HIGH_ADDR}, + {"PERI_SC_LOCAL_TIMER_COMP_LOW_ADDR ", + PERI_SC_LOCAL_TIMER_COMP_LOW_ADDR}, + {"PERI_SC_BAUD_VALUE_ADDR ", + PERI_SC_BAUD_VALUE_ADDR}, + {"PERI_SC_LOCAL_CNT_EN_ADDR ", + PERI_SC_LOCAL_CNT_EN_ADDR}, + {"PERI_SC_SYNC_ERR_COMP_HIGH_ADDR ", + PERI_SC_SYNC_ERR_COMP_HIGH_ADDR}, + {"PERI_SC_SYNC_ERR_COMP_LOW_ADDR ", + PERI_SC_SYNC_ERR_COMP_LOW_ADDR}, + {"PERI_SC_CRC_EN_ADDR ", + PERI_SC_CRC_EN_ADDR}, + {"PERI_SC_ONE_CYCLE_NUM_ADDR ", + PERI_SC_ONE_CYCLE_NUM_ADDR}, + {"PERI_SC_SYNC_ERR_CLR_ADDR ", + PERI_SC_SYNC_ERR_CLR_ADDR}, + {"PERI_SC_RX_SHIFT_EN_ADDR ", + PERI_SC_RX_SHIFT_EN_ADDR}, + {"PERI_SC_TIMEL_CY_NUM_ADDR ", + PERI_SC_TIMEL_CY_NUM_ADDR}, + {"PERI_SC_INT_PTP_SYNC_ERR_ADDR ", + PERI_SC_INT_PTP_SYNC_ERR_ADDR}, + {"PERI_SC_INT_PTP_SYNC_ERR_MASK_ADDR ", + PERI_SC_INT_PTP_SYNC_ERR_MASK_ADDR}, + {"PERI_SC_INT_ORIGIN ", + PERI_SC_INT_ORIGIN}, + {"PERI_SC_CRC_ERR_COUNT ", + PERI_SC_CRC_ERR_COUNT}, + {"PERI_SC_CRC_INT_CONTRL_ADDR ", + PERI_SC_CRC_INT_CONTRL_ADDR}, + {"PERI_SC_CAPTURE_PTP_TIME_COMP_HIGH ", + PERI_SC_CAPTURE_PTP_TIME_COMP_HIGH}, + {"PERI_SC_CAPTURE_PTP_TIME_COMP_LOW ", + PERI_SC_CAPTURE_PTP_TIME_COMP_LOW}, + {"PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_HIGH", + PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_HIGH}, + {"PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_LOW ", + PERI_SC_CAPTURE_SYSTEM_COUNTER_BIN_LOW}, + {"PERI_SC_CAPTURE_VLD ", + PERI_SC_CAPTURE_VLD}, + {"PERI_SC_LOCAL_TIME_LOW_ADDR ", + PERI_SC_LOCAL_TIME_LOW_ADDR}, + {"PERI_SC_LOCAL_TIME_HIGH_ADDR ", + PERI_SC_LOCAL_TIME_HIGH_ADDR} +}; + +static void hisi_ptp_dump_reg(void __iomem *base, + const struct hisi_ptp_reg *reg, int reg_len, + char *buf, int len, int *pos) +{ + int i; + + for (i = 0; i < reg_len; i++) + *pos += scnprintf(buf + *pos, len - *pos, "%s : 0x%08x\n", + reg[i].name, readl(base + reg[i].offset)); +} + +static ssize_t hisi_ptp_dbg_read_reg(struct file *filp, char __user *buf, + size_t cnt, loff_t *ppos) +{ + struct hisi_ptp_pdev *ptp = filp->private_data; + struct hisi_ptp_rx *rx; + unsigned long flags; + ssize_t size = 0; + char *read_buf; + int pos = 0; + int len; + + if (*ppos < 0) + return -EINVAL; + if (cnt == 0) + return 0; + if (!access_ok(buf, cnt)) + return -EFAULT; + + read_buf = kvzalloc(HISI_PTP_DBGFS_REG_LEN, GFP_KERNEL); + if (!read_buf) + return -ENOMEM; + + len = HISI_PTP_DBGFS_REG_LEN; + + write_lock_irqsave(&ptp->rw_lock, flags); + if (ptp->ptp_tx && ptp->ptp_tx->base) + hisi_ptp_dump_reg(ptp->ptp_tx->base, hisi_ptp_tx_reg, + ARRAY_SIZE(hisi_ptp_tx_reg), + read_buf, len, &pos); + + if (ptp->ptp_tx && ptp->ptp_tx->io_sc_base) + hisi_ptp_dump_reg(ptp->ptp_tx->io_sc_base, hisi_ptp_tx_io_reg, + ARRAY_SIZE(hisi_ptp_tx_io_reg), + read_buf, len, &pos); + + list_for_each_entry(rx, &ptp->ptp_rx_list, node) + hisi_ptp_dump_reg(rx->base, hisi_ptp_rx_reg, + ARRAY_SIZE(hisi_ptp_rx_reg), + read_buf, len, &pos); + + write_unlock_irqrestore(&ptp->rw_lock, flags); + + size = simple_read_from_buffer(buf, cnt, ppos, read_buf, + strlen(read_buf)); + + kvfree(read_buf); + + return size; +} + +static const struct file_operations hisi_ptp_dbg_state_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = hisi_ptp_dbg_read_state, +}; + +static const struct file_operations hisi_ptp_dbg_reg_ops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = hisi_ptp_dbg_read_reg, +}; + +static void hisi_ptp_dbgfs_init(struct hisi_ptp_pdev *ptp) +{ + ptp->dbgfs_root = debugfs_create_dir(HISI_PTP_NAME, NULL); + debugfs_create_file("state", 0400, ptp->dbgfs_root, ptp, + &hisi_ptp_dbg_state_ops); + debugfs_create_file("reg", 0400, ptp->dbgfs_root, ptp, + &hisi_ptp_dbg_reg_ops); +} + +static void hisi_ptp_dbgfs_uninit(struct hisi_ptp_pdev *ptp) +{ + debugfs_remove_recursive(ptp->dbgfs_root); +} + +static int __init hisi_ptp_module_init(void) +{ + struct hisi_ptp_pdev *ptp = &g_ptpdev; + int ret; + + memset(ptp, 0, sizeof(struct hisi_ptp_pdev)); + rwlock_init(&ptp->rw_lock); + INIT_LIST_HEAD(&ptp->ptp_rx_list); + + timer_setup(&ptp->suppress_timer, hisi_ptp_timer, 0); + + ret = platform_driver_register(&hisi_ptp_driver); + if (ret) { + del_timer_sync(&ptp->suppress_timer); + pr_err("failed to register ptp platform driver, ret = %d\n", + ret); + return ret; + } + + hisi_ptp_dbgfs_init(ptp); + + pr_info("hisi ptp platform driver inited, version: %s\n", + HISI_PTP_VERSION); + + return 0; +} +module_init(hisi_ptp_module_init); + +static void __exit hisi_ptp_module_exit(void) +{ + struct hisi_ptp_pdev *ptp = &g_ptpdev; + + pr_info("hisi ptp platform driver exit\n"); + + hisi_ptp_dbgfs_uninit(ptp); + + platform_driver_unregister(&hisi_ptp_driver); + + if (ptp->suppress_timer.function) + del_timer_sync(&ptp->suppress_timer); + + memset(ptp, 0, sizeof(struct hisi_ptp_pdev)); +} +module_exit(hisi_ptp_module_exit); + +MODULE_DESCRIPTION("HiSilicon PTP driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(HISI_PTP_VERSION);